@smwb/onreel-git-mcp 0.10.0 → 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 +48 -9
- package/dist/{chunk-AGGJK2XZ.js → chunk-6VQURMRA.js} +25 -1
- package/dist/{chunk-AGGJK2XZ.js.map → chunk-6VQURMRA.js.map} +1 -1
- package/dist/{chunk-ZTME4HFD.js → chunk-VLTG7OKJ.js} +2 -2
- package/dist/git/index.d.ts +1 -1
- package/dist/git/index.js +5 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel/log.types.d.ts +4 -1
- package/dist/kernel/redact.lib.d.ts +12 -2
- package/dist/kernel/store.types.d.ts +16 -2
- package/dist/mcp/index.js +1 -1
- package/dist/tracker/index.d.ts +1 -0
- package/dist/tracker/index.js +4 -2
- package/dist/tracker/index.js.map +1 -1
- package/dist/worktree/index.js +3 -3
- package/dist/worktree/index.js.map +1 -1
- package/package.json +1 -1
- /package/dist/{chunk-ZTME4HFD.js.map → chunk-VLTG7OKJ.js.map} +0 -0
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
|
-
|
|
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
|
|
@@ -204,7 +223,13 @@ await withWorktreeLock('wt-9', async () => {
|
|
|
204
223
|
все переходы (`markWorktreeReady`, `markWorktreeFailed`, `requestWorktreeCleanup`, `completeWorktreeCleanup`);
|
|
205
224
|
вложенность и асинхронные обратные вызовы не поддерживаются. **Эталон реализации — `test/helpers/memory-store.lib.ts`
|
|
206
225
|
в тестовом дереве пакета** (D-003): он же образец семантики для потребителей, включая копию-на-коммит в
|
|
207
|
-
`inTransaction` и отказ броска как откат.
|
|
226
|
+
`inTransaction` и отказ броска как откат. Свои переходы библиотека пишет с `actor: 'host'` — процесс, который её
|
|
227
|
+
держит (D-012); реализация порта отображает это значение в лексику потребителя.
|
|
228
|
+
|
|
229
|
+
Замки worktree — одна таблица на процесс. Субпуть, которому нужны замки, импортирует модуль жизненного цикла
|
|
230
|
+
напрямую: модуль двух и более точек входа tsup выносит в общий chunk, поэтому определение таблицы в собранном
|
|
231
|
+
пакете остаётся одно (D-013). Инвариант держит `test/worktree/locks-bundle.test.ts` — он собирает пакет
|
|
232
|
+
конфигурацией проекта во временный каталог и требует ровно одного определения.
|
|
208
233
|
|
|
209
234
|
## tracker
|
|
210
235
|
|
|
@@ -255,7 +280,13 @@ try {
|
|
|
255
280
|
`GITHUB_PRIORITY_LABELS`, `GITLAB_PRIORITY_LABELS`, …), чтобы потребитель видел, во что именно преводится его
|
|
256
281
|
статус. Свои трекеры добавляют в реестр явным `registerTracker(definition)`; повторный `kind` — исключение, а не
|
|
257
282
|
молчаливая замена. Типы порта `LocalIssueStore` (и его строк) реэкспортированы из `…/tracker` — реализацию, как и
|
|
258
|
-
для `WorktreeStore`, пишет потребитель; эталон в памяти — `test/helpers/tracker-fixtures.lib.ts`.
|
|
283
|
+
для `WorktreeStore`, пишет потребитель; эталон в памяти — `test/helpers/tracker-fixtures.lib.ts`. Порт требует:
|
|
284
|
+
комментарий — только к существующей задаче, иначе отказ с кодом `unknown_issue` (сиротских комментариев хранилище
|
|
285
|
+
не держит, D-011); `limit` страницы — целое от 1 до 500, по умолчанию 100, значение вне диапазона прижимается
|
|
286
|
+
к границам; снятый исполнитель (`assignee: null`) убирает ключ из строки задачи, а не оставляет его со значением
|
|
287
|
+
`undefined`. Нецелый `limit` до хранилища не доходит: адаптер отказывает `invalid_request` ещё в проверке запроса,
|
|
288
|
+
а чем на него отвечает сама реализация порта, не предписано — эталон в памяти отвечает `invalid_request`, ledger
|
|
289
|
+
падает ошибкой SQLite.
|
|
259
290
|
|
|
260
291
|
Таймаут запроса к трекеру — `TRACKER_TIMEOUT_MS` (15 с), тело ответа ошибки обрезают до
|
|
261
292
|
`TRACKER_ERROR_BODY_MAX` (300 символов) и маскируют; 404 с `missingIsIssue` — `unknown_issue`, а не «неверный
|
|
@@ -770,7 +801,8 @@ console.log(manifest.name, manifest.version);
|
|
|
770
801
|
`<dataDir>/tracker-secrets.json` с правами **0600** (каталог — 0700), запись идёт через временный файл и
|
|
771
802
|
`rename`, одиночная запись сериализуется очередью, чтобы два подключения не затерли друг друга. `deleteTrackerToken`
|
|
772
803
|
и пустая строка убирают значение. Наружу из хранилища выходит только признак «токен есть» (`hasTrackerToken`),
|
|
773
|
-
а при чтении и записи значение регистрируется в приёмнике
|
|
804
|
+
а при чтении и записи значение регистрируется в приёмнике секретов — redactor ядра и, если он задан,
|
|
805
|
+
приёмнике потребителя (см. «Маскирование секретов» ниже).
|
|
774
806
|
- **Токен — только своему origin.** `origin(connection)` в `TrackerDefinition` — origin API, куда адаптер (и его
|
|
775
807
|
разделы `pulls`, `ci` и `repo`) ходит с токеном: `https://api.github.com`, `https://gitlab.com`,
|
|
776
808
|
`https://api.sourcecraft.tech` по умолчанию, иначе origin `url` подключения; у Jira `url` обязателен. Потребитель
|
|
@@ -791,7 +823,11 @@ console.log(manifest.name, manifest.version);
|
|
|
791
823
|
на библиотеку: его применяют `GitError` (D-272), `requestJson` (тексты отказов трекера) и внутренний отказ
|
|
792
824
|
`serveMcp`. Свой журнал потребителя подключают приёмником `SecretSink`; известные значения добавляют до
|
|
793
825
|
форматирования текста, короткая строка (менее 8 символов) не регистрируется — она дала бы слишком много ложных
|
|
794
|
-
срабатываний.
|
|
826
|
+
срабатываний. `setSecretSink(sink)` (D-009) задаёт приёмник потребителя на весь процесс: известные секреты,
|
|
827
|
+
включая токены из хранилища, библиотека пишет и в свой redactor, и в `sink`, поэтому второй раз регистрировать
|
|
828
|
+
их не нужно, а тексты, которые библиотека отдаёт наружу сама, замаскированы и без журнала потребителя. Новый
|
|
829
|
+
`sink` сразу получает значения, встреченные до вызова. В `sink` значение уходит как есть: порог 8 символов
|
|
830
|
+
держит redactor ядра, свой порог потребитель задаёт сам.
|
|
795
831
|
- **Ограничение путей.** `checkRepoPath` отказывает базовому репозиторию, который абсолютным путём или по realpath
|
|
796
832
|
лежит внутри каталога данных (там ledger и токены), `prepareWorktreeParent` создаёт каталоги по одному и не
|
|
797
833
|
принимает симлинк, `checkManagedPath` перед удалением проверяет и сам каталог, и его realpath. `listWorktreeDir`
|
|
@@ -810,9 +846,12 @@ console.log(manifest.name, manifest.version);
|
|
|
810
846
|
- Публикуется собранный ESM и `.d.ts` из `dist/` (D-002) — суффиксы `.ts` в импортах исходников стирает сборка,
|
|
811
847
|
их не нужно править в потребителе.
|
|
812
848
|
- `sideEffects: false`, один рантайм-зависимый пакет — `zod`.
|
|
813
|
-
- Реестр трекеров, пул git, замки worktree
|
|
814
|
-
«хостов» в одном процессе делят их.
|
|
815
|
-
|
|
849
|
+
- Реестр трекеров, пул git, замки worktree, redactor и приёмник секретов потребителя (`setSecretSink`) —
|
|
850
|
+
модульная глобальна на процесс: несколько независимых «хостов» в одном процессе делят их. Субпути пакета
|
|
851
|
+
делят такое состояние, потому что модуль двух и более точек входа tsup выносит в общий chunk; свою копию
|
|
852
|
+
дал бы только бандл без splitting — инвариант одного определения в собранном пакете держит проверка (D-013).
|
|
853
|
+
Две независимо установленные копии пакета состояние не делят. Запросы к внешним API идут `fetch` из
|
|
854
|
+
контекста; узлы без TLS-цепей внешнего трекера — задача вызывающего.
|
|
816
855
|
- Имена `ONREEL_*` и `HOST_CONTRACT_VERSION` (`1.14`) заморожены: они держат interop с onreel.
|
|
817
856
|
|
|
818
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-
|
|
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-
|
|
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-
|
|
421
|
+
//# sourceMappingURL=chunk-VLTG7OKJ.js.map
|
package/dist/git/index.d.ts
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
23
|
-
*
|
|
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;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { IssuePriority, IssueStatusCategory, Worktree, WorktreeStatus } from '../contract/contract.types.ts';
|
|
2
2
|
/** Контекст записи: кто и когда; без них реализация берёт текущее время и не пишет actor. */
|
|
3
3
|
export type WorktreeWriteContext = {
|
|
4
|
+
/**
|
|
5
|
+
* Кто сделал переход. Собственные переходы библиотека пишет с actor `'host'` — процесс, который
|
|
6
|
+
* её держит (D-012); реализация потребителя отображает это значение в свою лексику.
|
|
7
|
+
*/
|
|
4
8
|
actor?: string | undefined;
|
|
5
9
|
ts?: number | undefined;
|
|
6
10
|
};
|
|
@@ -48,7 +52,13 @@ export type WorktreeStore = {
|
|
|
48
52
|
/** Ready worktree терминальных задач без живых сессий — их чистит reconciler. */
|
|
49
53
|
readyWorktreesOfSettledTasks(): Worktree[];
|
|
50
54
|
};
|
|
51
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Пагинация списков: всё скоупится и листается страницами, без неограниченных выборок.
|
|
57
|
+
* `limit` — целое от 1 до 500 (по умолчанию 100): значение вне диапазона реализация прижимает
|
|
58
|
+
* к границам. Нецелое значение до хранилища не доходит — адаптер отказывает `invalid_request`
|
|
59
|
+
* ещё в проверке запроса, поэтому код отказа реализации порта не предписан: эталон в памяти
|
|
60
|
+
* отвечает `invalid_request`, ledger падает ошибкой SQLite.
|
|
61
|
+
*/
|
|
52
62
|
export type PageInput = {
|
|
53
63
|
limit?: number | undefined;
|
|
54
64
|
cursor?: string | undefined;
|
|
@@ -102,8 +112,12 @@ export type LocalIssueStore = {
|
|
|
102
112
|
createLocalIssue(projectId: string, input: NewLocalIssue, ts?: number): LocalIssueRow;
|
|
103
113
|
getLocalIssue(projectId: string, number: number): LocalIssueRow | undefined;
|
|
104
114
|
listLocalIssues(projectId: string, input?: ListLocalIssuesInput): Page<LocalIssueRow>;
|
|
105
|
-
/** Нет задачи — undefined. */
|
|
115
|
+
/** Нет задачи — undefined. `assignee: null` снимает исполнителя и убирает ключ из строки. */
|
|
106
116
|
updateLocalIssue(projectId: string, number: number, patch: LocalIssuePatch, ts?: number): LocalIssueRow | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Комментарий к существующей задаче: задачи нет — отказ с кодом `unknown_issue`, сиротных
|
|
119
|
+
* комментариев порт не хранит (D-011).
|
|
120
|
+
*/
|
|
107
121
|
addLocalComment(projectId: string, number: number, body: string, author?: string, ts?: number): LocalCommentRow;
|
|
108
122
|
listLocalComments(projectId: string, number: number, input?: PageInput): Page<LocalCommentRow>;
|
|
109
123
|
};
|
package/dist/mcp/index.js
CHANGED
package/dist/tracker/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/tracker/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
REDACTED,
|
|
3
3
|
defaultSecretSink,
|
|
4
|
-
redactSecretText
|
|
5
|
-
|
|
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,
|