@smwb/onreel-git-mcp 0.10.1 → 0.11.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.
package/README.md CHANGED
@@ -121,7 +121,7 @@ R1–R8 (URL с `user:pass@`, `Authorization:`, известные ключи д
121
121
  в приёмнике секретов:
122
122
 
123
123
  ```ts
124
- import { defaultSecretSink, redactSecretText, type SecretSink } from '@smwb/onreel-git-mcp/git';
124
+ import { defaultSecretSink, redactSecretText, setSecretSink, type SecretSink } from '@smwb/onreel-git-mcp/git';
125
125
 
126
126
  defaultSecretSink.register(process.env.MYopaqueValue ?? ''); // короче 8 символов — игнорируется
127
127
  console.log(redactSecretText('клон https://user:секрет@git.example/o/r и MYopaqueValue'));
@@ -133,9 +133,28 @@ const sink: SecretSink = {
133
133
  known.add(secret);
134
134
  },
135
135
  };
136
+
137
+ // Одна точка на процесс: после этого вызова известные секреты получает и sink потребителя.
138
+ setSecretSink(sink);
139
+ setSecretSink(undefined); // убирает приёмник потребителя: redactor ядра эти значения уже знает
136
140
  ```
137
141
 
138
- Значения `SecretSink`, `defaultSecretSink` и `redactSecretText` реэкспортированы из `…/git`; отдельного
142
+ **Приёмник потребителя (D-009).** `setSecretSink(sink)` задаёт приёмник секретов потребителя на весь
143
+ процесс: значения, которые встречают адаптеры трекеров и хранилище токенов, библиотека пишет и в свой
144
+ redactor, и в `sink` — регистрировать их второй раз не нужно. Реестр ядра при этом не отключается:
145
+ тексты, которые библиотека отдаёт наружу сама (JSON-RPC-ошибка `serveMcp`, `GitError`, `TrackerError`
146
+ с телом ответа, окно лога CI), замаскированы и тогда, когда журнал потребителя их не видит. Новый `sink`
147
+ сразу получает значения, которые библиотека встретила до вызова, поэтому порядок «сначала токен, потом
148
+ `setSecretSink`» не оставляет секрета незамаскированным у потребителя. `setSecretSink(undefined)` убирает
149
+ приёмник потребителя; обратно переносить нечего — redactor ядра значения уже знает. Порог 8 символов
150
+ держит ядро, а в `sink` значение уходит как есть: свой порог потребитель задаёт сам. `sink` вправе
151
+ делегировать значение обратно в `defaultSecretSink` — так потребитель зеркалит секрет в реестр
152
+ библиотеки: внутри `register` самого приёмника библиотека обратный вызов пропускает, поэтому рекурсии
153
+ нет, а значение в реестре ядра оказывается до вызова приёмника. Вызов явный (R-001), состояние
154
+ модульное — как redactor и пул git, на процесс одно.
155
+
156
+ Значения `SecretSink`, `defaultSecretSink`, `redactSecretText` и `setSecretSink` реэкспортированы из
157
+ `…/git`; `setSecretSink` и тип `SecretSink` — также из `…/tracker`, рядом с хранилищем токенов. Отдельного
139
158
  субпути `…/kernel` у пакета нет.
140
159
 
141
160
  ## files
@@ -782,7 +801,8 @@ console.log(manifest.name, manifest.version);
782
801
  `<dataDir>/tracker-secrets.json` с правами **0600** (каталог — 0700), запись идёт через временный файл и
783
802
  `rename`, одиночная запись сериализуется очередью, чтобы два подключения не затерли друг друга. `deleteTrackerToken`
784
803
  и пустая строка убирают значение. Наружу из хранилища выходит только признак «токен есть» (`hasTrackerToken`),
785
- а при чтении и записи значение регистрируется в приёмнике секретов.
804
+ а при чтении и записи значение регистрируется в приёмнике секретов — redactor ядра и, если он задан,
805
+ приёмнике потребителя (см. «Маскирование секретов» ниже).
786
806
  - **Токен — только своему origin.** `origin(connection)` в `TrackerDefinition` — origin API, куда адаптер (и его
787
807
  разделы `pulls`, `ci` и `repo`) ходит с токеном: `https://api.github.com`, `https://gitlab.com`,
788
808
  `https://api.sourcecraft.tech` по умолчанию, иначе origin `url` подключения; у Jira `url` обязателен. Потребитель
@@ -803,7 +823,11 @@ console.log(manifest.name, manifest.version);
803
823
  на библиотеку: его применяют `GitError` (D-272), `requestJson` (тексты отказов трекера) и внутренний отказ
804
824
  `serveMcp`. Свой журнал потребителя подключают приёмником `SecretSink`; известные значения добавляют до
805
825
  форматирования текста, короткая строка (менее 8 символов) не регистрируется — она дала бы слишком много ложных
806
- срабатываний.
826
+ срабатываний. `setSecretSink(sink)` (D-009) задаёт приёмник потребителя на весь процесс: известные секреты,
827
+ включая токены из хранилища, библиотека пишет и в свой redactor, и в `sink`, поэтому второй раз регистрировать
828
+ их не нужно, а тексты, которые библиотека отдаёт наружу сама, замаскированы и без журнала потребителя. Новый
829
+ `sink` сразу получает значения, встреченные до вызова. В `sink` значение уходит как есть: порог 8 символов
830
+ держит redactor ядра, свой порог потребитель задаёт сам.
807
831
  - **Ограничение путей.** `checkRepoPath` отказывает базовому репозиторию, который абсолютным путём или по realpath
808
832
  лежит внутри каталога данных (там ledger и токены), `prepareWorktreeParent` создаёт каталоги по одному и не
809
833
  принимает симлинк, `checkManagedPath` перед удалением проверяет и сам каталог, и его realpath. `listWorktreeDir`
@@ -822,11 +846,12 @@ console.log(manifest.name, manifest.version);
822
846
  - Публикуется собранный ESM и `.d.ts` из `dist/` (D-002) — суффиксы `.ts` в импортах исходников стирает сборка,
823
847
  их не нужно править в потребителе.
824
848
  - `sideEffects: false`, один рантайм-зависимый пакет — `zod`.
825
- - Реестр трекеров, пул git, замки worktree и redactor — модульная глобальна на процесс: несколько независимых
826
- «хостов» в одном процессе делят их. Субпути пакета делят такое состояние, потому что модуль двух и более точек
827
- входа tsup выносит в общий chunk; свою копию дал бы только бандл без splitting — инвариант одного определения
828
- в собранном пакете держит проверка (D-013). Две независимо установленные копии пакета состояние не делят.
829
- Запросы к внешним API идут `fetch` из контекста; узлы без TLS-цепей внешнего трекера — задача вызывающего.
849
+ - Реестр трекеров, пул git, замки worktree, redactor и приёмник секретов потребителя (`setSecretSink`) —
850
+ модульная глобальна на процесс: несколько независимых «хостов» в одном процессе делят их. Субпути пакета
851
+ делят такое состояние, потому что модуль двух и более точек входа tsup выносит в общий chunk; свою копию
852
+ дал бы только бандл без splitting — инвариант одного определения в собранном пакете держит проверка (D-013).
853
+ Две независимо установленные копии пакета состояние не делят. Запросы к внешним API идут `fetch` из
854
+ контекста; узлы без TLS-цепей внешнего трекера — задача вызывающего.
830
855
  - Имена `ONREEL_*` и `HOST_CONTRACT_VERSION` (`1.14`) заморожены: они держат interop с onreel.
831
856
 
832
857
  ## Как внести изменение
@@ -153,15 +153,39 @@ var kernelRedactor = createRedactor();
153
153
  function redactSecretText(text) {
154
154
  return kernelRedactor.redactText(text);
155
155
  }
156
+ var consumerSecretSink;
157
+ var seenSecrets = [];
158
+ var dispatchingToConsumer = false;
159
+ function rememberSecret(secret) {
160
+ if (!seenSecrets.includes(secret)) seenSecrets = [...seenSecrets, secret];
161
+ }
162
+ function dispatchToConsumer(secret) {
163
+ const sink = consumerSecretSink;
164
+ if (sink === void 0 || dispatchingToConsumer) return;
165
+ dispatchingToConsumer = true;
166
+ try {
167
+ sink.register(secret);
168
+ } finally {
169
+ dispatchingToConsumer = false;
170
+ }
171
+ }
172
+ function setSecretSink(sink) {
173
+ consumerSecretSink = sink;
174
+ if (sink === void 0) return;
175
+ for (const secret of seenSecrets) dispatchToConsumer(secret);
176
+ }
156
177
  var defaultSecretSink = {
157
178
  register(secret) {
179
+ rememberSecret(secret);
158
180
  kernelRedactor.addKnownSecret(secret);
181
+ dispatchToConsumer(secret);
159
182
  }
160
183
  };
161
184
 
162
185
  export {
163
186
  REDACTED,
164
187
  redactSecretText,
188
+ setSecretSink,
165
189
  defaultSecretSink
166
190
  };
167
- //# sourceMappingURL=chunk-AGGJK2XZ.js.map
191
+ //# sourceMappingURL=chunk-6VQURMRA.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/kernel/redact.lib.ts"],"sourcesContent":["// Маскирование секретов в текстах библиотеки @smwb/onreel-git-mcp (заморожено в части B выделения).\n// Происхождение — onreel @ b73407be076a5df73931d1340f70910ca13c908d:\n// src/contract/log/log.lib.ts — движок createRedactor (правила R1–R8), дословная копия;\n// src/contract/log/log.const.ts — REDACTED, SECRET_KEY_PATTERN, дословная копия;\n// src/contract/log/log.types.ts — тип Redactor, дословная копия;\n// src/host/redact.lib.ts — redactSecretText; в onreel она работает на redactor'е журнала host\n// (hostLogger().redactor), которого в библиотеке нет, поэтому здесь — общий redactor уровня\n// модуля; регистрация известных секретов идёт через defaultSecretSink (шов SecretSink,\n// kernel/log.types.ts) на том же redactor'е — семантика onreel сохранена 1:1.\n// Побочных эффектов при импорте нет: redactor — чистый объект в памяти (R-001).\nimport type { SecretSink } from './log.types.ts';\n\nexport const REDACTED = '***';\n\n/** Имя ключа секретное, если после приведения к snake_case оно содержит одно из слов целиком. */\nexport const SECRET_KEY_PATTERN =\n /(?:^|[^a-z])(?:token|secrets?|passw(?:or)?d|pass|passphrase|pwd|api_?key|apikey|authorization|auth|cookie|set_cookie|credentials?|private_?key|access_?key|session_?id|signature)(?:$|[^a-z])/;\n\n/** Redactor журнала: маскирует свободный текст и структурные значения, принимает известные секреты. */\nexport type Redactor = {\n /** Маскирует свободный текст: известные значения и шаблоны R1–R8. */\n redactText(text: string): string;\n /** Глубокая копия: ключи по isSecretKey → '***' (любое значение, кроме null и boolean); строки и сами имена ключей → redactText. */\n redactValue(value: unknown): unknown;\n /** Регистрирует известное значение секрета; короче 8 символов — игнорируется. */\n addKnownSecret(value: string): void;\n};\n\n// ---------------------------------------------------------------------------------------------\n// Маскирование (D-194)\n// ---------------------------------------------------------------------------------------------\n\n/** Однозначные слова как окончание слитного имени: `PGPASSWORD`, `MYSQLPASSWORD`, `APITOKEN`, `dbSecret`. */\nconst SECRET_KEY_SUFFIX = /(?:token|secret|passw(?:or)?d|passphrase|apikey)$/;\n/** Голое `key` как последнее слово — секрет только у имён в стиле env (`OPENAI_KEY`): у `push_key`, `manifest_key` это идентификаторы. */\nconst ENV_KEY_SUFFIX = /(?:^|_)key$/;\n\nconst secretKeyCache = new Map<string, boolean>();\nconst SECRET_KEY_CACHE_MAX = 1_000;\n/** Длинные имена в кеш не попадают: ключ data размером с мегабайт, помноженный на 1000 записей, держал бы гигабайт. */\nconst SECRET_KEY_CACHE_KEY_MAX_CHARS = 128;\n\n/** camelCase → snake, '-' и '.' → '_', нижний регистр, затем SECRET_KEY_PATTERN и окончания слитных имён. */\nexport function isSecretKey(key: string): boolean {\n const cacheable = key.length <= SECRET_KEY_CACHE_KEY_MAX_CHARS;\n const cached = cacheable ? secretKeyCache.get(key) : undefined;\n if (cached !== undefined) return cached;\n const normalized = key\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[-.]/g, '_')\n .toLowerCase();\n const envStyle = !/[a-z]/.test(key);\n const secret = SECRET_KEY_PATTERN.test(normalized) || SECRET_KEY_SUFFIX.test(normalized) || (envStyle && ENV_KEY_SUFFIX.test(normalized));\n if (cacheable) {\n if (secretKeyCache.size >= SECRET_KEY_CACHE_MAX) secretKeyCache.clear();\n secretKeyCache.set(key, secret);\n }\n return secret;\n}\n\nconst MIN_KNOWN_SECRET_CHARS = 8;\n\n// Шаблоны R1–R7 применяются после R8. Все линейные по длине входа: у каждого начало совпадения\n// привязано к границе (\\b или lookbehind), чтобы длинный ряд символов не перебирался с каждой позиции.\nconst PEM_PRIVATE_KEY = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g;\nconst ONREEL_TOKEN = /onr_[a-z]+_[A-Za-z0-9_-]{8,}/g;\nconst VENDOR_KEY =\n /\\b(?:sk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{30,}|xox[abposr]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|glpat-[A-Za-z0-9_-]{20,})\\b/g;\nconst JWT = /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\\.eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}/g;\n// Перед схемой допустим один маркер `+`/`-`, если он начинает токен (начало строки, несхемный символ или\n// SGR-escape): так маскируется URL в строке diff, в том числе цветной (`\\x1b[32m+https://u:p@h`).\nconst URL_USERINFO =\n // oxlint-disable-next-line no-control-regex -- ESC перед маркером: строка цветного diff.\n /(?:(?<![a-z0-9+.-])|(?<=(?:^|[^a-z0-9+.-]|\\x1b\\[[0-9;]*m)[+-]))([a-z][a-z0-9+.-]{0,31}:\\/\\/)[^\\s/@:]+:[^\\s/@]+@/gim;\nconst AUTH_SCHEME = /\\b(Bearer|Basic|Token)\\s+[A-Za-z0-9._~+/=-]{8,}/gi;\n// R3 разбит на поиск «ключ + разделитель» и ручное чтение значения: значение несекретного ключа\n// не съедается, и `url=http://h/?access_token=abc` всё равно доходит до access_token.\nconst KEY_ASSIGNMENT = /(?<![A-Za-z0-9_.-])([A-Za-z][A-Za-z0-9_.-]{0,63})([\"']?\\s*[=:]\\s*)([\"']?)/g;\nconst ASSIGNMENT_VALUE = /((?:Bearer|Basic|Token)[ \\t]+)?[^\\s\"'&,;}]+/iy;\n// Значение cookie — вся строка заголовка: `Cookie: theme=dark; sid=…`, а не до первой `;`.\nconst COOKIE_VALUE = /[^\\r\\n]+/y;\nconst COOKIE_KEY = /^(?:set[_.-]?)?cookie$/i;\n// Грубый предфильтр R3: нет ни одного слова из SECRET_KEY_PATTERN — разбирать присваивания незачем.\nconst SECRET_KEYWORD = /token|secret|pass|pwd|key|auth|cookie|credential|session[_.-]?id|signature/i;\n// Пароль в аргументах curl и подобных: `-u user:pass`, `-uuser:pass`, `--user user:pass`, `--user=user:pass`.\n// Имя пользователя остаётся. Здесь только голова до двоеточия: классы разделителя и имени не пересекаются\n// (общий `[ \\t=]+` и `[^\\s:\"']+` оба принимали `=` и давали квадратичный перебор на ` -u ====…`), а сам\n// пароль читает maskCliPasswords, чтобы пароль в кавычках с пробелами маскировался целиком.\nconst CLI_USER_HEAD = /(?<![^\\s])(?:-u[ \\t]*|--user(?:[ \\t]+|=))([\"']?)([^\\s:\"']+):/g;\nconst CLI_PASSWORD = /[^\\s\"']+/y;\n/** Потолок значения в кавычках: поиск закрывающей кавычки остаётся линейным по входу. */\nconst QUOTED_VALUE_MAX_CHARS = 4_096;\n\n/**\n * Конец значения в кавычках: позиция закрывающей кавычки того же вида, `\\`-экранирование учитывается.\n * Закрывающей нет в пределах потолка — значение режется по потолку или концу текста: лучше замаскировать\n * лишнее, чем оставить хвост секрета.\n */\nfunction quotedValueEnd(text: string, start: number, quote: string): number {\n const limit = Math.min(text.length, start + QUOTED_VALUE_MAX_CHARS);\n for (let index = start; index < limit; index += 1) {\n const char = text[index];\n if (char === '\\\\') index += 1;\n else if (char === quote) return index;\n }\n return limit;\n}\n\n/** Пароль после `-u user:` / `--user user:`: слово без кавычек либо значение в кавычках целиком, с пробелами внутри. */\nfunction maskCliPasswords(text: string): string {\n if (!text.includes('-u')) return text;\n const headPattern = new RegExp(CLI_USER_HEAD.source, 'g');\n const passwordPattern = new RegExp(CLI_PASSWORD.source, 'y');\n let result = '';\n let copied = 0;\n for (let head = headPattern.exec(text); head !== null; head = headPattern.exec(text)) {\n let quote = head[1] ?? '';\n let passwordStart = head.index + head[0].length;\n // Кавычки только вокруг пароля: `-u admin:\"two words\"`.\n const next = text[passwordStart];\n if (quote === '' && (next === '\"' || next === \"'\")) {\n quote = next;\n passwordStart += 1;\n }\n let passwordEnd: number;\n if (quote !== '') {\n passwordEnd = quotedValueEnd(text, passwordStart, quote);\n } else {\n passwordPattern.lastIndex = passwordStart;\n const password = passwordPattern.exec(text);\n passwordEnd = password === null ? passwordStart : passwordStart + password[0].length;\n }\n // Пустой пароль маскировать нечем; поиск продолжается сразу за головой.\n if (passwordEnd === passwordStart) continue;\n result += text.slice(copied, passwordStart) + REDACTED;\n copied = passwordEnd;\n headPattern.lastIndex = copied;\n }\n return copied === 0 ? text : result + text.slice(copied);\n}\n\nfunction maskAssignments(text: string): string {\n if (!SECRET_KEYWORD.test(text)) return text;\n const keyPattern = new RegExp(KEY_ASSIGNMENT.source, 'g');\n const valuePattern = new RegExp(ASSIGNMENT_VALUE.source, 'iy');\n const cookiePattern = new RegExp(COOKIE_VALUE.source, 'y');\n let result = '';\n let copied = 0;\n for (let match = keyPattern.exec(text); match !== null; match = keyPattern.exec(text)) {\n const key = match[1] ?? '';\n if (!isSecretKey(key)) continue;\n const quote = match[3] ?? '';\n const valueStart = match.index + match[0].length;\n let scheme = '';\n let valueEnd: number;\n if (quote !== '') {\n // Значение в кавычках целиком, с пробелами внутри; закрывающая кавычка остаётся в тексте.\n valueEnd = quotedValueEnd(text, valueStart, quote);\n if (valueEnd === valueStart) continue;\n } else {\n const isCookie = COOKIE_KEY.test(key);\n const pattern = isCookie ? cookiePattern : valuePattern;\n pattern.lastIndex = valueStart;\n const valueMatch = pattern.exec(text);\n if (valueMatch === null) continue;\n scheme = isCookie ? '' : (valueMatch[1] ?? '');\n valueEnd = valueStart + valueMatch[0].length;\n }\n result += text.slice(copied, valueStart) + scheme + REDACTED;\n copied = valueEnd;\n keyPattern.lastIndex = copied;\n }\n return copied === 0 ? text : result + text.slice(copied);\n}\n\nexport function createRedactor(options: { knownSecrets?: readonly string[] } = {}): Redactor {\n // Длинные значения первыми: короткое значение не должно оставить хвост длинного, которое его содержит.\n let known: string[] = [];\n const addKnownSecret = (value: string): void => {\n if (value.length < MIN_KNOWN_SECRET_CHARS || known.includes(value)) return;\n known = [...known, value].sort((a, b) => b.length - a.length);\n };\n for (const value of options.knownSecrets ?? []) addKnownSecret(value);\n\n const redactText = (text: string): string => {\n let out = text;\n for (const secret of known) {\n // Токен onreel и как известное значение остаётся `onr_***`: так его узнают по журналу (D-047).\n if (out.includes(secret)) out = out.split(secret).join(secret.startsWith('onr_') ? 'onr_***' : REDACTED);\n }\n out = out.replace(PEM_PRIVATE_KEY, `-----BEGIN PRIVATE KEY-----${REDACTED}`);\n out = out.replace(ONREEL_TOKEN, 'onr_***');\n out = out.replace(VENDOR_KEY, REDACTED);\n out = out.replace(JWT, REDACTED);\n out = out.replace(URL_USERINFO, `$1${REDACTED}@`);\n out = out.replace(AUTH_SCHEME, `$1 ${REDACTED}`);\n return maskAssignments(maskCliPasswords(out));\n };\n\n const MAX_VALUE_DEPTH = 32;\n const redactValue = (value: unknown, ancestors: object[] = []): unknown => {\n if (typeof value === 'string') return redactText(value);\n if (value === null || typeof value !== 'object') return value;\n if (ancestors.includes(value)) return '[Circular]';\n if (ancestors.length >= MAX_VALUE_DEPTH) return '[Object]';\n const path = [...ancestors, value];\n if (Array.isArray(value)) return value.map((item) => redactValue(item, path));\n const copy: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n const item = (value as Record<string, unknown>)[key];\n // Сам ключ тоже текст: словарь «токен → состояние» не должен выдать токен именем ключа.\n let maskedKey = redactText(key);\n // Два разных секрета дают одно замаскированное имя: суффикс сохраняет обе записи.\n for (let n = 2; Object.hasOwn(copy, maskedKey); n += 1) maskedKey = `${redactText(key)}#${String(n)}`;\n setOwn(copy, maskedKey, isSecretKey(key) && item !== null && typeof item !== 'boolean' ? REDACTED : redactValue(item, path));\n }\n return copy;\n };\n\n return { redactText, redactValue: (value) => redactValue(value), addKnownSecret };\n}\n\n/** Присваивание без сеттера прототипа: ключ `__proto__` из данных остаётся обычным ключом. */\nfunction setOwn(target: Record<string, unknown>, key: string, value: unknown): void {\n Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });\n}\n\n// ---------------------------------------------------------------------------------------------\n// Redactor уровня библиотеки: общий для redactSecretText и defaultSecretSink, как redactor\n// журнала host в onreel общий для redactSecretText и registerLogSecret.\n// ---------------------------------------------------------------------------------------------\n\nconst kernelRedactor = createRedactor();\n\n/** Свободный текст через redactor ядра: известные секреты (defaultSecretSink) и шаблоны R1–R8. */\nexport function redactSecretText(text: string): string {\n return kernelRedactor.redactText(text);\n}\n\n/**\n * Эталонный SecretSink библиотеки: регистрирует известные секреты в redactor ядра, которым\n * маскирует redactSecretText. Потребитель со своим журналом может подменить шов своей реализацией.\n */\nexport const defaultSecretSink: SecretSink = {\n register(secret: string): void {\n kernelRedactor.addKnownSecret(secret);\n },\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/kernel/redact.lib.ts"],"sourcesContent":["// Маскирование секретов в текстах библиотеки @smwb/onreel-git-mcp (заморожено в части B выделения).\n// Происхождение — onreel @ b73407be076a5df73931d1340f70910ca13c908d:\n// src/contract/log/log.lib.ts — движок createRedactor (правила R1–R8), дословная копия;\n// src/contract/log/log.const.ts — REDACTED, SECRET_KEY_PATTERN, дословная копия;\n// src/contract/log/log.types.ts — тип Redactor, дословная копия;\n// src/host/redact.lib.ts — redactSecretText; в onreel она работает на redactor'е журнала host\n// (hostLogger().redactor), которого в библиотеке нет, поэтому здесь — общий redactor уровня\n// модуля; регистрация известных секретов идёт через defaultSecretSink (шов SecretSink,\n// kernel/log.types.ts) на том же redactor'е — семантика onreel сохранена 1:1.\n// Побочных эффектов при импорте нет: redactor — чистый объект в памяти (R-001).\nimport type { SecretSink } from './log.types.ts';\n\nexport const REDACTED = '***';\n\n/** Имя ключа секретное, если после приведения к snake_case оно содержит одно из слов целиком. */\nexport const SECRET_KEY_PATTERN =\n /(?:^|[^a-z])(?:token|secrets?|passw(?:or)?d|pass|passphrase|pwd|api_?key|apikey|authorization|auth|cookie|set_cookie|credentials?|private_?key|access_?key|session_?id|signature)(?:$|[^a-z])/;\n\n/** Redactor журнала: маскирует свободный текст и структурные значения, принимает известные секреты. */\nexport type Redactor = {\n /** Маскирует свободный текст: известные значения и шаблоны R1–R8. */\n redactText(text: string): string;\n /** Глубокая копия: ключи по isSecretKey → '***' (любое значение, кроме null и boolean); строки и сами имена ключей → redactText. */\n redactValue(value: unknown): unknown;\n /** Регистрирует известное значение секрета; короче 8 символов — игнорируется. */\n addKnownSecret(value: string): void;\n};\n\n// ---------------------------------------------------------------------------------------------\n// Маскирование (D-194)\n// ---------------------------------------------------------------------------------------------\n\n/** Однозначные слова как окончание слитного имени: `PGPASSWORD`, `MYSQLPASSWORD`, `APITOKEN`, `dbSecret`. */\nconst SECRET_KEY_SUFFIX = /(?:token|secret|passw(?:or)?d|passphrase|apikey)$/;\n/** Голое `key` как последнее слово — секрет только у имён в стиле env (`OPENAI_KEY`): у `push_key`, `manifest_key` это идентификаторы. */\nconst ENV_KEY_SUFFIX = /(?:^|_)key$/;\n\nconst secretKeyCache = new Map<string, boolean>();\nconst SECRET_KEY_CACHE_MAX = 1_000;\n/** Длинные имена в кеш не попадают: ключ data размером с мегабайт, помноженный на 1000 записей, держал бы гигабайт. */\nconst SECRET_KEY_CACHE_KEY_MAX_CHARS = 128;\n\n/** camelCase → snake, '-' и '.' → '_', нижний регистр, затем SECRET_KEY_PATTERN и окончания слитных имён. */\nexport function isSecretKey(key: string): boolean {\n const cacheable = key.length <= SECRET_KEY_CACHE_KEY_MAX_CHARS;\n const cached = cacheable ? secretKeyCache.get(key) : undefined;\n if (cached !== undefined) return cached;\n const normalized = key\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[-.]/g, '_')\n .toLowerCase();\n const envStyle = !/[a-z]/.test(key);\n const secret = SECRET_KEY_PATTERN.test(normalized) || SECRET_KEY_SUFFIX.test(normalized) || (envStyle && ENV_KEY_SUFFIX.test(normalized));\n if (cacheable) {\n if (secretKeyCache.size >= SECRET_KEY_CACHE_MAX) secretKeyCache.clear();\n secretKeyCache.set(key, secret);\n }\n return secret;\n}\n\nconst MIN_KNOWN_SECRET_CHARS = 8;\n\n// Шаблоны R1–R7 применяются после R8. Все линейные по длине входа: у каждого начало совпадения\n// привязано к границе (\\b или lookbehind), чтобы длинный ряд символов не перебирался с каждой позиции.\nconst PEM_PRIVATE_KEY = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g;\nconst ONREEL_TOKEN = /onr_[a-z]+_[A-Za-z0-9_-]{8,}/g;\nconst VENDOR_KEY =\n /\\b(?:sk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{30,}|xox[abposr]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|glpat-[A-Za-z0-9_-]{20,})\\b/g;\nconst JWT = /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\\.eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}/g;\n// Перед схемой допустим один маркер `+`/`-`, если он начинает токен (начало строки, несхемный символ или\n// SGR-escape): так маскируется URL в строке diff, в том числе цветной (`\\x1b[32m+https://u:p@h`).\nconst URL_USERINFO =\n // oxlint-disable-next-line no-control-regex -- ESC перед маркером: строка цветного diff.\n /(?:(?<![a-z0-9+.-])|(?<=(?:^|[^a-z0-9+.-]|\\x1b\\[[0-9;]*m)[+-]))([a-z][a-z0-9+.-]{0,31}:\\/\\/)[^\\s/@:]+:[^\\s/@]+@/gim;\nconst AUTH_SCHEME = /\\b(Bearer|Basic|Token)\\s+[A-Za-z0-9._~+/=-]{8,}/gi;\n// R3 разбит на поиск «ключ + разделитель» и ручное чтение значения: значение несекретного ключа\n// не съедается, и `url=http://h/?access_token=abc` всё равно доходит до access_token.\nconst KEY_ASSIGNMENT = /(?<![A-Za-z0-9_.-])([A-Za-z][A-Za-z0-9_.-]{0,63})([\"']?\\s*[=:]\\s*)([\"']?)/g;\nconst ASSIGNMENT_VALUE = /((?:Bearer|Basic|Token)[ \\t]+)?[^\\s\"'&,;}]+/iy;\n// Значение cookie — вся строка заголовка: `Cookie: theme=dark; sid=…`, а не до первой `;`.\nconst COOKIE_VALUE = /[^\\r\\n]+/y;\nconst COOKIE_KEY = /^(?:set[_.-]?)?cookie$/i;\n// Грубый предфильтр R3: нет ни одного слова из SECRET_KEY_PATTERN — разбирать присваивания незачем.\nconst SECRET_KEYWORD = /token|secret|pass|pwd|key|auth|cookie|credential|session[_.-]?id|signature/i;\n// Пароль в аргументах curl и подобных: `-u user:pass`, `-uuser:pass`, `--user user:pass`, `--user=user:pass`.\n// Имя пользователя остаётся. Здесь только голова до двоеточия: классы разделителя и имени не пересекаются\n// (общий `[ \\t=]+` и `[^\\s:\"']+` оба принимали `=` и давали квадратичный перебор на ` -u ====…`), а сам\n// пароль читает maskCliPasswords, чтобы пароль в кавычках с пробелами маскировался целиком.\nconst CLI_USER_HEAD = /(?<![^\\s])(?:-u[ \\t]*|--user(?:[ \\t]+|=))([\"']?)([^\\s:\"']+):/g;\nconst CLI_PASSWORD = /[^\\s\"']+/y;\n/** Потолок значения в кавычках: поиск закрывающей кавычки остаётся линейным по входу. */\nconst QUOTED_VALUE_MAX_CHARS = 4_096;\n\n/**\n * Конец значения в кавычках: позиция закрывающей кавычки того же вида, `\\`-экранирование учитывается.\n * Закрывающей нет в пределах потолка — значение режется по потолку или концу текста: лучше замаскировать\n * лишнее, чем оставить хвост секрета.\n */\nfunction quotedValueEnd(text: string, start: number, quote: string): number {\n const limit = Math.min(text.length, start + QUOTED_VALUE_MAX_CHARS);\n for (let index = start; index < limit; index += 1) {\n const char = text[index];\n if (char === '\\\\') index += 1;\n else if (char === quote) return index;\n }\n return limit;\n}\n\n/** Пароль после `-u user:` / `--user user:`: слово без кавычек либо значение в кавычках целиком, с пробелами внутри. */\nfunction maskCliPasswords(text: string): string {\n if (!text.includes('-u')) return text;\n const headPattern = new RegExp(CLI_USER_HEAD.source, 'g');\n const passwordPattern = new RegExp(CLI_PASSWORD.source, 'y');\n let result = '';\n let copied = 0;\n for (let head = headPattern.exec(text); head !== null; head = headPattern.exec(text)) {\n let quote = head[1] ?? '';\n let passwordStart = head.index + head[0].length;\n // Кавычки только вокруг пароля: `-u admin:\"two words\"`.\n const next = text[passwordStart];\n if (quote === '' && (next === '\"' || next === \"'\")) {\n quote = next;\n passwordStart += 1;\n }\n let passwordEnd: number;\n if (quote !== '') {\n passwordEnd = quotedValueEnd(text, passwordStart, quote);\n } else {\n passwordPattern.lastIndex = passwordStart;\n const password = passwordPattern.exec(text);\n passwordEnd = password === null ? passwordStart : passwordStart + password[0].length;\n }\n // Пустой пароль маскировать нечем; поиск продолжается сразу за головой.\n if (passwordEnd === passwordStart) continue;\n result += text.slice(copied, passwordStart) + REDACTED;\n copied = passwordEnd;\n headPattern.lastIndex = copied;\n }\n return copied === 0 ? text : result + text.slice(copied);\n}\n\nfunction maskAssignments(text: string): string {\n if (!SECRET_KEYWORD.test(text)) return text;\n const keyPattern = new RegExp(KEY_ASSIGNMENT.source, 'g');\n const valuePattern = new RegExp(ASSIGNMENT_VALUE.source, 'iy');\n const cookiePattern = new RegExp(COOKIE_VALUE.source, 'y');\n let result = '';\n let copied = 0;\n for (let match = keyPattern.exec(text); match !== null; match = keyPattern.exec(text)) {\n const key = match[1] ?? '';\n if (!isSecretKey(key)) continue;\n const quote = match[3] ?? '';\n const valueStart = match.index + match[0].length;\n let scheme = '';\n let valueEnd: number;\n if (quote !== '') {\n // Значение в кавычках целиком, с пробелами внутри; закрывающая кавычка остаётся в тексте.\n valueEnd = quotedValueEnd(text, valueStart, quote);\n if (valueEnd === valueStart) continue;\n } else {\n const isCookie = COOKIE_KEY.test(key);\n const pattern = isCookie ? cookiePattern : valuePattern;\n pattern.lastIndex = valueStart;\n const valueMatch = pattern.exec(text);\n if (valueMatch === null) continue;\n scheme = isCookie ? '' : (valueMatch[1] ?? '');\n valueEnd = valueStart + valueMatch[0].length;\n }\n result += text.slice(copied, valueStart) + scheme + REDACTED;\n copied = valueEnd;\n keyPattern.lastIndex = copied;\n }\n return copied === 0 ? text : result + text.slice(copied);\n}\n\nexport function createRedactor(options: { knownSecrets?: readonly string[] } = {}): Redactor {\n // Длинные значения первыми: короткое значение не должно оставить хвост длинного, которое его содержит.\n let known: string[] = [];\n const addKnownSecret = (value: string): void => {\n if (value.length < MIN_KNOWN_SECRET_CHARS || known.includes(value)) return;\n known = [...known, value].sort((a, b) => b.length - a.length);\n };\n for (const value of options.knownSecrets ?? []) addKnownSecret(value);\n\n const redactText = (text: string): string => {\n let out = text;\n for (const secret of known) {\n // Токен onreel и как известное значение остаётся `onr_***`: так его узнают по журналу (D-047).\n if (out.includes(secret)) out = out.split(secret).join(secret.startsWith('onr_') ? 'onr_***' : REDACTED);\n }\n out = out.replace(PEM_PRIVATE_KEY, `-----BEGIN PRIVATE KEY-----${REDACTED}`);\n out = out.replace(ONREEL_TOKEN, 'onr_***');\n out = out.replace(VENDOR_KEY, REDACTED);\n out = out.replace(JWT, REDACTED);\n out = out.replace(URL_USERINFO, `$1${REDACTED}@`);\n out = out.replace(AUTH_SCHEME, `$1 ${REDACTED}`);\n return maskAssignments(maskCliPasswords(out));\n };\n\n const MAX_VALUE_DEPTH = 32;\n const redactValue = (value: unknown, ancestors: object[] = []): unknown => {\n if (typeof value === 'string') return redactText(value);\n if (value === null || typeof value !== 'object') return value;\n if (ancestors.includes(value)) return '[Circular]';\n if (ancestors.length >= MAX_VALUE_DEPTH) return '[Object]';\n const path = [...ancestors, value];\n if (Array.isArray(value)) return value.map((item) => redactValue(item, path));\n const copy: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n const item = (value as Record<string, unknown>)[key];\n // Сам ключ тоже текст: словарь «токен → состояние» не должен выдать токен именем ключа.\n let maskedKey = redactText(key);\n // Два разных секрета дают одно замаскированное имя: суффикс сохраняет обе записи.\n for (let n = 2; Object.hasOwn(copy, maskedKey); n += 1) maskedKey = `${redactText(key)}#${String(n)}`;\n setOwn(copy, maskedKey, isSecretKey(key) && item !== null && typeof item !== 'boolean' ? REDACTED : redactValue(item, path));\n }\n return copy;\n };\n\n return { redactText, redactValue: (value) => redactValue(value), addKnownSecret };\n}\n\n/** Присваивание без сеттера прототипа: ключ `__proto__` из данных остаётся обычным ключом. */\nfunction setOwn(target: Record<string, unknown>, key: string, value: unknown): void {\n Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });\n}\n\n// ---------------------------------------------------------------------------------------------\n// Redactor уровня библиотеки: общий для redactSecretText и defaultSecretSink, как redactor\n// журнала host в onreel общий для redactSecretText и registerLogSecret. Приёмник потребителя на\n// весь процесс (setSecretSink, D-009) — добавление библиотеки: в onreel секрет регистрирует\n// registerLogSecret журнала host напрямую, а потребитель библиотеки иначе писал бы значение дважды.\n// ---------------------------------------------------------------------------------------------\n\nconst kernelRedactor = createRedactor();\n\n/** Свободный текст через redactor ядра: известные секреты (defaultSecretSink) и шаблоны R1–R8. */\nexport function redactSecretText(text: string): string {\n return kernelRedactor.redactText(text);\n}\n\n// Приёмник потребителя — состояние модуля, как redactor: на процесс один действующий.\nlet consumerSecretSink: SecretSink | undefined;\n// Значения, которые библиотека уже встретила: новый приёмник получает их сразу, иначе секрет,\n// встреченный до setSecretSink, в маскировке потребителя не появился бы (D-009).\nlet seenSecrets: string[] = [];\n// Защита от повторного входа: приёмник потребителя вправе делегировать значение обратно в\n// defaultSecretSink — так он зеркалит секрет в реестр библиотеки, как делал до появления\n// setSecretSink, — и без флага цепочка «библиотека → приёмник → библиотека» не завершается. Флаг\n// общий для обоих путей записи в приёмник: и defaultSecretSink.register, и перенос встреченных\n// значений в setSecretSink вызывают register потребителя.\nlet dispatchingToConsumer = false;\n\nfunction rememberSecret(secret: string): void {\n if (!seenSecrets.includes(secret)) seenSecrets = [...seenSecrets, secret];\n}\n\n/**\n * Отдаёт значение в приёмник потребителя, если он задан. Внутри `register` самого приёмника вызов\n * пропускается: пропустить нечего, вложенный вызов исходит от приёмника, значит значение у него уже\n * есть. В redactor ядра значение записано до этого вызова, поэтому маскировка текстов библиотеки не\n * зависит от того, дошёл ли обратный вызов.\n */\nfunction dispatchToConsumer(secret: string): void {\n const sink = consumerSecretSink;\n if (sink === undefined || dispatchingToConsumer) return;\n dispatchingToConsumer = true;\n try {\n sink.register(secret);\n } finally {\n // Бросивший приёмник не должен оставлять флаг: иначе следующие секреты молча перестали бы до него доходить.\n dispatchingToConsumer = false;\n }\n}\n\n/**\n * Задаёт приёмник секретов потребителя на весь процесс (D-009): известные значения библиотека пишет\n * и в свой redactor, и в `sink`, поэтому потребитель со своим журналом не регистрирует их сам. `sink`\n * сразу получает значения, которые библиотека встретила до вызова. `undefined` убирает приёмник\n * потребителя; redactor ядра эти значения уже знает, поэтому маскировка текстов библиотеки не\n * меняется. Приёмник может делегировать обратно в `defaultSecretSink`: перенос значений защищён от\n * повторного входа так же, как запись нового секрета. Вызов явный (R-001).\n */\nexport function setSecretSink(sink: SecretSink | undefined): void {\n consumerSecretSink = sink;\n if (sink === undefined) return;\n for (const secret of seenSecrets) dispatchToConsumer(secret);\n}\n\n/**\n * Приёмник секретов библиотеки: известное значение пополняет redactor ядра — им redactSecretText\n * маскирует тексты, которые библиотека отдаёт наружу сама, — и уходит в приёмник потребителя\n * (setSecretSink), если он задан. Его параметром по умолчанию берут функции хранилища токенов трекеров.\n */\nexport const defaultSecretSink: SecretSink = {\n register(secret: string): void {\n rememberSecret(secret);\n kernelRedactor.addKnownSecret(secret);\n dispatchToConsumer(secret);\n },\n};\n"],"mappings":"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","names":[]}
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-3YP22YLA.js";
8
8
  import {
9
9
  redactSecretText
10
- } from "./chunk-AGGJK2XZ.js";
10
+ } from "./chunk-6VQURMRA.js";
11
11
 
12
12
  // src/git/git.const.ts
13
13
  var GIT_TIMEOUT_MS = 12e4;
@@ -418,4 +418,4 @@ export {
418
418
  showBlob,
419
419
  readWorktreeFile
420
420
  };
421
- //# sourceMappingURL=chunk-ZTME4HFD.js.map
421
+ //# sourceMappingURL=chunk-VLTG7OKJ.js.map
@@ -4,4 +4,4 @@ export * from './git.lib.ts';
4
4
  export * from './paths.const.ts';
5
5
  export * from './paths.lib.ts';
6
6
  export type { SecretSink } from '../kernel/log.types.ts';
7
- export { defaultSecretSink, redactSecretText } from '../kernel/redact.lib.ts';
7
+ export { defaultSecretSink, redactSecretText, setSecretSink } from '../kernel/redact.lib.ts';
package/dist/git/index.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  showBlob,
26
26
  statusEntries,
27
27
  untrackedFiles
28
- } from "../chunk-ZTME4HFD.js";
28
+ } from "../chunk-VLTG7OKJ.js";
29
29
  import {
30
30
  PROJECT_WORKTREE_SEGMENT,
31
31
  PathEscapeError,
@@ -46,8 +46,9 @@ import {
46
46
  import "../chunk-3YP22YLA.js";
47
47
  import {
48
48
  defaultSecretSink,
49
- redactSecretText
50
- } from "../chunk-AGGJK2XZ.js";
49
+ redactSecretText,
50
+ setSecretSink
51
+ } from "../chunk-6VQURMRA.js";
51
52
  import "../chunk-4ZGRXIVA.js";
52
53
  export {
53
54
  GIT_CHECKOUT_TIMEOUT_MS,
@@ -87,6 +88,7 @@ export {
87
88
  resolveCommit,
88
89
  runGit,
89
90
  safeComponent,
91
+ setSecretSink,
90
92
  showBlob,
91
93
  statusEntries,
92
94
  trashRoot,
package/dist/index.js CHANGED
@@ -59,7 +59,7 @@ import {
59
59
  // package.json
60
60
  var package_default = {
61
61
  name: "@smwb/onreel-git-mcp",
62
- version: "0.10.1",
62
+ version: "0.11.0",
63
63
  description: "\u0411\u0438\u0431\u043B\u0438\u043E\u0442\u0435\u043A\u0430 \u043C\u043E\u0434\u0443\u043B\u0435\u0439, \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u043D\u044B\u0445 \u0438\u0437 onreel: git-\u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u044B\u0435 \u043F\u0443\u0442\u0438, \u0441\u043F\u0438\u0441\u043E\u043A \u0444\u0430\u0439\u043B\u043E\u0432 worktree, \u0436\u0438\u0437\u043D\u0435\u043D\u043D\u044B\u0439 \u0446\u0438\u043A\u043B worktree \u043F\u0440\u043E\u0442\u0438\u0432 \u043F\u043E\u0440\u0442\u0430 WorktreeStore, \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u044B \u0442\u0440\u0435\u043A\u0435\u0440\u043E\u0432 GitHub/GitLab/Jira/SourceCraft/\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043D\u0430 stdio",
64
64
  keywords: [
65
65
  "git",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@smwb/onreel-git-mcp\",\n \"version\": \"0.10.1\",\n \"description\": \"Библиотека модулей, выделенных из onreel: git-операции и безопасные пути, список файлов worktree, жизненный цикл worktree против порта WorktreeStore, адаптеры трекеров GitHub/GitLab/Jira/SourceCraft/локального и MCP-сервер на stdio\",\n \"keywords\": [\n \"git\",\n \"worktree\",\n \"mcp\",\n \"issue-tracker\",\n \"github\",\n \"gitlab\",\n \"jira\",\n \"sourcecraft\",\n \"onreel\",\n \"typescript\"\n ],\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=24\"\n },\n \"files\": [\n \"dist\"\n ],\n \"sideEffects\": false,\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://git.sourcecraft.dev/elfenmail/onreel-git-mcp.git\"\n },\n \"homepage\": \"https://git.sourcecraft.dev/elfenmail/onreel-git-mcp\",\n \"license\": \"MIT\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"default\": \"./dist/index.js\"\n },\n \"./git\": {\n \"types\": \"./dist/git/index.d.ts\",\n \"import\": \"./dist/git/index.js\",\n \"default\": \"./dist/git/index.js\"\n },\n \"./files\": {\n \"types\": \"./dist/files/index.d.ts\",\n \"import\": \"./dist/files/index.js\",\n \"default\": \"./dist/files/index.js\"\n },\n \"./worktree\": {\n \"types\": \"./dist/worktree/index.d.ts\",\n \"import\": \"./dist/worktree/index.js\",\n \"default\": \"./dist/worktree/index.js\"\n },\n \"./tracker\": {\n \"types\": \"./dist/tracker/index.d.ts\",\n \"import\": \"./dist/tracker/index.js\",\n \"default\": \"./dist/tracker/index.js\"\n },\n \"./mcp\": {\n \"types\": \"./dist/mcp/index.d.ts\",\n \"import\": \"./dist/mcp/index.js\",\n \"default\": \"./dist/mcp/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"build\": \"tsup && tsc -p tsconfig.build.json\",\n \"typecheck\": \"tsc -p tsconfig.json && tsc -p tsconfig.examples.json\",\n \"lint\": \"oxlint --type-aware\",\n \"test\": \"node --test \\\"test/**/*.test.ts\\\"\",\n \"changeset\": \"changeset\",\n \"version\": \"changeset version\",\n \"release\": \"npm run build && changeset publish\"\n },\n \"dependencies\": {\n \"zod\": \"^4.6.5\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"2.29.2\",\n \"@types/node\": \"24.19.1\",\n \"oxlint\": \"1.87.0\",\n \"oxlint-tsgolint\": \"7.0.2003\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"7.0.2\"\n }\n}\n","// Корневой подпуть пакета @smwb/onreel-git-mcp: срез контракта (константы, схемы, типы, чистые\n// функции) и версия пакета. Всё без побочных эффектов при импорте (R-001). Швы ядра\n// (SecretSink, WorktreeStore, LocalIssueStore) и модули git/files/worktree/tracker/mcp\n// экспортируют свои подпути — см. exports в package.json и README.\nimport pkg from '../package.json' with { type: 'json' };\n\nexport * from './contract/contract.const.ts';\nexport * from './contract/contract.schema.ts';\nexport * from './contract/contract.types.ts';\nexport * from './contract/contract.lib.ts';\n\n// Версия пакета — из version в package.json: статический импорт JSON, его значение tsup подставляет\n// в бандл на сборке, поэтому VERSION не расходится с релизом changesets (тот правит только version).\nexport const VERSION: string = pkg.version;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,MAAQ;AAAA,EACR,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,OAAS;AAAA,IACP;AAAA,EACF;AAAA,EACA,aAAe;AAAA,EACf,eAAiB;AAAA,IACf,QAAU;AAAA,IACV,UAAY;AAAA,EACd;AAAA,EACA,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,UAAY;AAAA,EACZ,SAAW;AAAA,EACX,SAAW;AAAA,IACT,KAAK;AAAA,MACH,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,WAAW;AAAA,MACT,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,cAAc;AAAA,MACZ,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,aAAa;AAAA,MACX,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,kBAAkB;AAAA,EACpB;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,WAAa;AAAA,IACb,SAAW;AAAA,IACX,SAAW;AAAA,EACb;AAAA,EACA,cAAgB;AAAA,IACd,KAAO;AAAA,EACT;AAAA,EACA,iBAAmB;AAAA,IACjB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,QAAU;AAAA,IACV,mBAAmB;AAAA,IACnB,MAAQ;AAAA,IACR,YAAc;AAAA,EAChB;AACF;;;AC1EO,IAAM,UAAkB,gBAAI;","names":[]}
1
+ {"version":3,"sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@smwb/onreel-git-mcp\",\n \"version\": \"0.11.0\",\n \"description\": \"Библиотека модулей, выделенных из onreel: git-операции и безопасные пути, список файлов worktree, жизненный цикл worktree против порта WorktreeStore, адаптеры трекеров GitHub/GitLab/Jira/SourceCraft/локального и MCP-сервер на stdio\",\n \"keywords\": [\n \"git\",\n \"worktree\",\n \"mcp\",\n \"issue-tracker\",\n \"github\",\n \"gitlab\",\n \"jira\",\n \"sourcecraft\",\n \"onreel\",\n \"typescript\"\n ],\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=24\"\n },\n \"files\": [\n \"dist\"\n ],\n \"sideEffects\": false,\n \"publishConfig\": {\n \"access\": \"public\",\n \"registry\": \"https://registry.npmjs.org/\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://git.sourcecraft.dev/elfenmail/onreel-git-mcp.git\"\n },\n \"homepage\": \"https://git.sourcecraft.dev/elfenmail/onreel-git-mcp\",\n \"license\": \"MIT\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"default\": \"./dist/index.js\"\n },\n \"./git\": {\n \"types\": \"./dist/git/index.d.ts\",\n \"import\": \"./dist/git/index.js\",\n \"default\": \"./dist/git/index.js\"\n },\n \"./files\": {\n \"types\": \"./dist/files/index.d.ts\",\n \"import\": \"./dist/files/index.js\",\n \"default\": \"./dist/files/index.js\"\n },\n \"./worktree\": {\n \"types\": \"./dist/worktree/index.d.ts\",\n \"import\": \"./dist/worktree/index.js\",\n \"default\": \"./dist/worktree/index.js\"\n },\n \"./tracker\": {\n \"types\": \"./dist/tracker/index.d.ts\",\n \"import\": \"./dist/tracker/index.js\",\n \"default\": \"./dist/tracker/index.js\"\n },\n \"./mcp\": {\n \"types\": \"./dist/mcp/index.d.ts\",\n \"import\": \"./dist/mcp/index.js\",\n \"default\": \"./dist/mcp/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"build\": \"tsup && tsc -p tsconfig.build.json\",\n \"typecheck\": \"tsc -p tsconfig.json && tsc -p tsconfig.examples.json\",\n \"lint\": \"oxlint --type-aware\",\n \"test\": \"node --test \\\"test/**/*.test.ts\\\"\",\n \"changeset\": \"changeset\",\n \"version\": \"changeset version\",\n \"release\": \"npm run build && changeset publish\"\n },\n \"dependencies\": {\n \"zod\": \"^4.6.5\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"2.29.2\",\n \"@types/node\": \"24.19.1\",\n \"oxlint\": \"1.87.0\",\n \"oxlint-tsgolint\": \"7.0.2003\",\n \"tsup\": \"8.5.1\",\n \"typescript\": \"7.0.2\"\n }\n}\n","// Корневой подпуть пакета @smwb/onreel-git-mcp: срез контракта (константы, схемы, типы, чистые\n// функции) и версия пакета. Всё без побочных эффектов при импорте (R-001). Швы ядра\n// (SecretSink, WorktreeStore, LocalIssueStore) и модули git/files/worktree/tracker/mcp\n// экспортируют свои подпути — см. exports в package.json и README.\nimport pkg from '../package.json' with { type: 'json' };\n\nexport * from './contract/contract.const.ts';\nexport * from './contract/contract.schema.ts';\nexport * from './contract/contract.types.ts';\nexport * from './contract/contract.lib.ts';\n\n// Версия пакета — из version в package.json: статический импорт JSON, его значение tsup подставляет\n// в бандл на сборке, поэтому VERSION не расходится с релизом changesets (тот правит только version).\nexport const VERSION: string = pkg.version;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,aAAe;AAAA,EACf,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,MAAQ;AAAA,EACR,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,OAAS;AAAA,IACP;AAAA,EACF;AAAA,EACA,aAAe;AAAA,EACf,eAAiB;AAAA,IACf,QAAU;AAAA,IACV,UAAY;AAAA,EACd;AAAA,EACA,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,UAAY;AAAA,EACZ,SAAW;AAAA,EACX,SAAW;AAAA,IACT,KAAK;AAAA,MACH,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,WAAW;AAAA,MACT,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,cAAc;AAAA,MACZ,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,aAAa;AAAA,MACX,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,SAAS;AAAA,MACP,OAAS;AAAA,MACT,QAAU;AAAA,MACV,SAAW;AAAA,IACb;AAAA,IACA,kBAAkB;AAAA,EACpB;AAAA,EACA,SAAW;AAAA,IACT,OAAS;AAAA,IACT,WAAa;AAAA,IACb,MAAQ;AAAA,IACR,MAAQ;AAAA,IACR,WAAa;AAAA,IACb,SAAW;AAAA,IACX,SAAW;AAAA,EACb;AAAA,EACA,cAAgB;AAAA,IACd,KAAO;AAAA,EACT;AAAA,EACA,iBAAmB;AAAA,IACjB,mBAAmB;AAAA,IACnB,eAAe;AAAA,IACf,QAAU;AAAA,IACV,mBAAmB;AAAA,IACnB,MAAQ;AAAA,IACR,YAAc;AAAA,EAChB;AACF;;;AC1EO,IAAM,UAAkB,gBAAI;","names":[]}
@@ -1,5 +1,8 @@
1
1
  /** Приёмник известных секретов: значение маскируется в текстах ошибок и журналов. */
2
2
  export type SecretSink = {
3
- /** Регистрирует значение секрета; значение короче 8 символов реализация игнорирует. */
3
+ /**
4
+ * Регистрирует значение секрета. Библиотека передаёт его как есть, в том числе короткое: порог
5
+ * 8 символов держит redactor ядра, а свой порог реализация задаёт сама.
6
+ */
4
7
  register(secret: string): void;
5
8
  };
@@ -19,7 +19,17 @@ export declare function createRedactor(options?: {
19
19
  /** Свободный текст через redactor ядра: известные секреты (defaultSecretSink) и шаблоны R1–R8. */
20
20
  export declare function redactSecretText(text: string): string;
21
21
  /**
22
- * Эталонный SecretSink библиотеки: регистрирует известные секреты в redactor ядра, которым
23
- * маскирует redactSecretText. Потребитель со своим журналом может подменить шов своей реализацией.
22
+ * Задаёт приёмник секретов потребителя на весь процесс (D-009): известные значения библиотека пишет
23
+ * и в свой redactor, и в `sink`, поэтому потребитель со своим журналом не регистрирует их сам. `sink`
24
+ * сразу получает значения, которые библиотека встретила до вызова. `undefined` убирает приёмник
25
+ * потребителя; redactor ядра эти значения уже знает, поэтому маскировка текстов библиотеки не
26
+ * меняется. Приёмник может делегировать обратно в `defaultSecretSink`: перенос значений защищён от
27
+ * повторного входа так же, как запись нового секрета. Вызов явный (R-001).
28
+ */
29
+ export declare function setSecretSink(sink: SecretSink | undefined): void;
30
+ /**
31
+ * Приёмник секретов библиотеки: известное значение пополняет redactor ядра — им redactSecretText
32
+ * маскирует тексты, которые библиотека отдаёт наружу сама, — и уходит в приёмник потребителя
33
+ * (setSecretSink), если он задан. Его параметром по умолчанию берут функции хранилища токенов трекеров.
24
34
  */
25
35
  export declare const defaultSecretSink: SecretSink;
package/dist/mcp/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "../chunk-3YP22YLA.js";
5
5
  import {
6
6
  redactSecretText
7
- } from "../chunk-AGGJK2XZ.js";
7
+ } from "../chunk-6VQURMRA.js";
8
8
  import {
9
9
  ENV_DISPATCH_ID,
10
10
  ENV_HOST_URL,
@@ -31,3 +31,4 @@ export * from './gitlab-repo.lib.ts';
31
31
  export * from './sourcecraft-repo.lib.ts';
32
32
  export type { ListLocalIssuesInput, LocalCommentRow, LocalIssuePatch, LocalIssueRow, LocalIssueStore, NewLocalIssue, Page, PageInput, WorktreeCleanupFlags, WorktreeCleanupInput, WorktreeStore, WorktreeTx, WorktreeWriteContext, } from '../kernel/store.types.ts';
33
33
  export type { SecretSink } from '../kernel/log.types.ts';
34
+ export { setSecretSink } from '../kernel/redact.lib.ts';
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  REDACTED,
3
3
  defaultSecretSink,
4
- redactSecretText
5
- } from "../chunk-AGGJK2XZ.js";
4
+ redactSecretText,
5
+ setSecretSink
6
+ } from "../chunk-6VQURMRA.js";
6
7
  import {
7
8
  issueLinkSchema,
8
9
  timestampSchema
@@ -3390,6 +3391,7 @@ export {
3390
3391
  requestJson,
3391
3392
  requestText,
3392
3393
  requireTarget,
3394
+ setSecretSink,
3393
3395
  sourcecraftCi,
3394
3396
  sourcecraftPulls,
3395
3397
  sourcecraftRepo,