@rt-tools/agent-kit 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +8 -2
  2. package/assets/checks/board.github.mjs +1 -1
  3. package/assets/checks/check-board.github.mjs +14 -0
  4. package/assets/checks/check-doc-paths.mjs +24 -5
  5. package/assets/checks/check-file-size.mjs +8 -2
  6. package/assets/checks/check-prose-style.mjs +10 -1
  7. package/assets/defaults/gate-map.sh +13 -0
  8. package/assets/defaults/project.sh +10 -0
  9. package/assets/defaults/shell.sh +18 -3
  10. package/assets/hooks/browser-guard-device-id.sh +42 -12
  11. package/assets/hooks/dispatch.sh +40 -9
  12. package/assets/hooks/docs-guard.sh +10 -0
  13. package/assets/hooks/exam-guard.sh +66 -16
  14. package/assets/hooks/git-guard-delivery-draft.sh +78 -0
  15. package/assets/hooks/git-guard-delivery.sh +49 -111
  16. package/assets/hooks/git-guard-main.sh +39 -4
  17. package/assets/hooks/git-guard-push-tests.sh +49 -3
  18. package/assets/hooks/hook-input.sh +17 -6
  19. package/assets/hooks/rule-source-guard.sh +11 -0
  20. package/assets/hooks/stand-login-guard.sh +101 -0
  21. package/assets/hooks/write-targets.sh +37 -4
  22. package/assets/laws/verifiability.md +12 -2
  23. package/assets/laws/work-conduct.md +59 -65
  24. package/assets/patterns/browser-verification-measure.md +41 -1
  25. package/assets/patterns/browser-verification-stand.md +56 -15
  26. package/assets/patterns/doc-style-human.md +75 -0
  27. package/assets/patterns/git-workflow-commit.azure.md +12 -0
  28. package/assets/patterns/git-workflow-commit.github.md +16 -3
  29. package/assets/patterns/git-workflow-commit.gitlab.md +12 -0
  30. package/assets/patterns/git-workflow-merge.md +8 -0
  31. package/assets/patterns/task-flow-start.md +1 -1
  32. package/assets/patterns/testing-e2e.md +18 -8
  33. package/assets/pitfalls/task-flow.md +40 -40
  34. package/assets/rules/browser-verification.md +29 -3
  35. package/assets/rules/doc-style.md +36 -0
  36. package/assets/rules/git-workflow.github.md +20 -23
  37. package/assets/rules/reuse-first.md +8 -2
  38. package/assets/rules/styling-bem.md +8 -1
  39. package/assets/rules/task-flow.md +73 -73
  40. package/assets/rules/testing.md +21 -0
  41. package/assets/skills/agent-kit.md +60 -70
  42. package/lib/commands.d.ts.map +1 -1
  43. package/lib/commands.js +63 -2
  44. package/lib/commands.js.map +1 -1
  45. package/lib/enroll.d.ts.map +1 -1
  46. package/lib/enroll.js +1 -1
  47. package/lib/enroll.js.map +1 -1
  48. package/lib/observations.d.ts +10 -1
  49. package/lib/observations.d.ts.map +1 -1
  50. package/lib/observations.js +1 -0
  51. package/lib/observations.js.map +1 -1
  52. package/lib/override-marks.d.ts +24 -0
  53. package/lib/override-marks.d.ts.map +1 -0
  54. package/lib/override-marks.js +98 -0
  55. package/lib/override-marks.js.map +1 -0
  56. package/lib/shipment.d.ts.map +1 -1
  57. package/lib/shipment.js +1 -1
  58. package/lib/shipment.js.map +1 -1
  59. package/package.json +1 -1
  60. package/rt-tools-agent-kit-0.24.0.tgz +0 -0
  61. package/rt-tools-agent-kit-0.23.0.tgz +0 -0
package/lib/enroll.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"enroll.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/enroll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Заведение дерева: токен ложится на диск правами «читает и пишет только владелец файла».\n *\n * Путей два, и человек выбирает доводом.\n *\n * Обмен по коду — дерево просит токен у приёма, неся одноразовый код. Это вторая и последняя\n * команда пакета, которая ходит в сеть, и единственная, которая ходит туда без токена: токена у\n * дерева ещё нет, за ним и обращаются.\n *\n * Выданный токен — тот, что человек получил прямо в админке приёма. Обменивать не на что, и в\n * сеть команда не идёт вовсе: она кладёт полученное на диск теми же правами. Отсюда и разный\n * набор проверок — адрес приёма и запрет открытого пути этому пути не нужны.\n *\n * Ходит она только по слову человека. Гарды в сеть не ходят вовсе, и заведение — не исключение:\n * дерево заводят один раз, руками.\n *\n * Сеть здесь отделена от решения так же, как у отправки груза: чем сходить, приходит доводом, и\n * спека подставляет двойник, никуда не стучась.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { IEnrollGranted } from './cargo.js';\nimport { intakeUrl, SHIP_TIMEOUT_MS } from './ship.js';\n\n/** Права файла с токеном: читает и пишет только владелец файла. */\nconst TOKEN_MODE: number = 0o600;\n\n/** Тело обращения: признак дерева и код приглашения. Имени в нём нет — его знает приглашение. */\nexport interface IEnrollBody {\n readonly schema: string;\n readonly tree: string;\n readonly code: string;\n}\n\n/** Чем кончилось обращение. Отказ — такой же ответ, как выданный токен: его печатает вызывающий. */\nexport interface IEnrollAnswer {\n readonly ok: boolean;\n /** Код ответа. Ноль — приём не ответил вовсе: не дозвонились, оборвалось, вышло время. */\n readonly status: number;\n readonly said: string;\n /** Токен и имя дерева. Пусто у отказа: приём называет их только у годного обращения. */\n readonly granted: IEnrollGranted | null;\n}\n\n/** Чем обращение уходит в приём. Двойник в спеке — того же вида. */\nexport type TEnrollCall = (intake: string, body: IEnrollBody) => Promise<IEnrollAnswer>;\n\n/** Что нужно заведению: где дерево, куда идти, чем платить и куда класть токен. */\nexport interface IEnrollOptions {\n readonly root: string;\n readonly intake: string;\n readonly code: string;\n /**\n * Токен, выданный человеку прямо в админке приёма.\n *\n * Второй путь завести дерево, и сети он не требует вовсе: токен уже на руках, обменивать не\n * на что. С кодом приглашения не сочетается — это два пути к одному и тому же.\n */\n readonly issued: string;\n /** Признак дерева, посчитанный от адреса репозитория. */\n readonly tree: string;\n /** Файл токена — путь от корня дерева или от домашнего каталога. */\n readonly token: string;\n /** Перезаписать лежащий токен намеренно. Без него дерево с токеном обращения не шлёт. */\n readonly force: boolean;\n readonly call: TEnrollCall;\n}\n\n/** Что команда напечатала и чем кончилась. Строки печатает вызывающий. */\nexport interface IEnrollOutcome {\n readonly code: number;\n readonly lines: readonly string[];\n}\n\n/** Отказ команды: причина строками и ненулевой код выхода. */\nfunction refusal(...lines: readonly string[]): IEnrollOutcome {\n return { code: 1, lines };\n}\n\n/** Что сказал приём: его собственное сообщение, а если тело не разбирается — тело как приехало. */\nfunction saidOf(text: string): string {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['message'] === 'string' ? said['message'] : text.trim();\n } catch {\n return text.trim();\n }\n}\n\n/** Токен и имя из ответа приёма. Ответ не тем — пусто: своё устройство приём не пересказывает. */\nfunction grantedOf(text: string): IEnrollGranted | null {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['token'] === 'string' && typeof said['tree'] === 'string' && typeof said['name'] === 'string'\n ? { tree: said['tree'], name: said['name'], token: said['token'] }\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Годен ли адрес приёма для обращения без TLS.\n *\n * Токен уходит единственным ответом на это обращение, и по открытому пути его читает всякий, кто\n * стоит между сторонами. Исключение одно — локальная машина: там приём поднимают для проверки, и\n * сети между сторонами нет вовсе.\n */\nexport function intakeAllowed(intake: string): boolean {\n const address: string = intake.trim().toLowerCase();\n\n if (address.startsWith('https://')) {\n return true;\n }\n\n return /^http:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])([:/]|$)/.test(address);\n}\n\n/** Куда ложится токен: путь от домашнего каталога или от корня дерева. */\nexport function tokenPath(root: string, spoken: string): string {\n return spoken.startsWith('~/') ? join(homedir(), spoken.slice(2)) : join(root, spoken);\n}\n\n/** Запись токена на диск: у обоих путей она одна, и права файла у неё одни. */\nfunction lay(path: string, token: string): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${token}\\n`, { encoding: 'utf8', mode: TOKEN_MODE });\n}\n\n/** Токен с рук: приём о нём не спрашивается — он же его и выдал, и обменивать не на что. */\nfunction accept(options: IEnrollOptions, path: string): IEnrollOutcome {\n lay(path, options.issued);\n\n return {\n code: 0,\n lines: [\n 'дерево заведено токеном, выданным в админке приёма',\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'проверить связь с приёмом: `agent-kit propose --dry-run`',\n ],\n };\n}\n\n/** Обращение и запись токена: сюда доходит только то, что прошло проверки у себя. */\nasync function grant(options: IEnrollOptions, path: string): Promise<IEnrollOutcome> {\n const answer: IEnrollAnswer = await options.call(options.intake, { schema: '1', tree: options.tree, code: options.code });\n\n if (!answer.ok || !answer.granted) {\n return refusal(answer.status === 0 ? `приём не ответил: ${answer.said}` : `приём отказал (${answer.status}): ${answer.said}`);\n }\n\n lay(path, answer.granted.token);\n\n return {\n code: 0,\n lines: [\n `дерево заведено: «${answer.granted.name}» (${answer.granted.tree})`,\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'приглашение погашено: второй раз им не воспользоваться',\n ],\n };\n}\n\n/**\n * Выбран ли путь: отказ, когда доводов два или ни одного, и пусто, когда выбран ровно один.\n *\n * Стоит отдельно от самого заведения: разбор довода и порядок проверок — два разных решения, и\n * вместе они не читаются.\n */\nfunction pathChosen(options: IEnrollOptions): IEnrollOutcome | null {\n if (options.code && options.issued) {\n return refusal(\n 'код приглашения и выданный токен — два разных пути завести дерево, и вместе они не идут',\n 'оставьте один довод: --code <код> либо --token <токен>'\n );\n }\n\n if (!options.code && !options.issued) {\n return refusal(\n 'заведение ждёт одно из двух:',\n ' --code <код> код приглашения, выданный владельцем приёма',\n ' --token <токен> токен, выданный человеку прямо в админке приёма'\n );\n }\n\n return null;\n}\n\n/** Годен ли приём для обмена по коду. Пути с рук эти проверки не касаются: он в сеть не идёт. */\nfunction intakeReady(options: IEnrollOptions): IEnrollOutcome | null {\n if (!options.intake) {\n return refusal('в настройке дерева не назван адрес приёма: заполните ключ `intake`');\n }\n\n if (!intakeAllowed(options.intake)) {\n return refusal(\n `адрес приёма ${options.intake} без TLS: токен уходит единственным ответом, и по открытому пути его прочитает всякий`,\n 'поправьте ключ `intake` на https://… — исключение сделано только для локальной машины'\n );\n }\n\n return null;\n}\n\n/**\n * Заведение дерева.\n *\n * Порядок проверок такой, что до сети доходит только то, что имеет шанс: незаполненная настройка,\n * открытый адрес и уже лежащий токен отбиваются здесь, у себя. Общие обоим путям проверки стоят\n * до развилки: поставленные после, они защищали бы только один из них.\n */\nexport async function enroll(options: IEnrollOptions): Promise<IEnrollOutcome> {\n const chosen: IEnrollOutcome | null = pathChosen(options);\n\n if (chosen) {\n return chosen;\n }\n\n if (!options.token) {\n return refusal('в настройке дерева не назван файл токена: заполните ключ `token`');\n }\n\n const laid: string = tokenPath(options.root, options.token);\n\n if (existsSync(laid) && !options.force) {\n return refusal(\n `токен уже лежит в ${laid}: заведённое дерево потеряло бы связь со своим прежним грузом`,\n 'если это намеренно — позовите ту же команду с доводом --force'\n );\n }\n\n if (options.issued) {\n return accept(options, laid);\n }\n\n const reachable: IEnrollOutcome | null = intakeReady(options);\n\n return reachable ?? grant(options, laid);\n}\n\n/** Обращение в приём по-настоящему. Код уезжает телом и в заголовки не попадает. */\nexport const httpEnroll: TEnrollCall = async (intake: string, body: IEnrollBody): Promise<IEnrollAnswer> => {\n let answer: Response;\n\n try {\n answer = await fetch(intakeUrl(intake, 'enroll'), {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(SHIP_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n return { ok: false, status: 0, said: (error as Error).message, granted: null };\n }\n\n const text: string = await answer.text();\n\n return { ok: answer.ok, status: answer.status, said: saidOf(text), granted: answer.ok ? grantedOf(text) : null };\n};\n"]}
1
+ {"version":3,"file":"enroll.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/enroll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Заведение дерева: токен ложится на диск правами «читает и пишет только владелец файла».\n *\n * Путей два, и человек выбирает доводом.\n *\n * Обмен по коду — дерево просит токен у приёма, неся одноразовый код. Это вторая и последняя\n * команда пакета, которая ходит в сеть, и единственная, которая ходит туда без токена: токена у\n * дерева ещё нет, за ним и обращаются.\n *\n * Выданный токен — тот, что человек получил прямо в админке приёма. Обменивать не на что, и в\n * сеть команда не идёт вовсе: она кладёт полученное на диск теми же правами. Отсюда и разный\n * набор проверок — адрес приёма и запрет открытого пути этому пути не нужны.\n *\n * Ходит она только по слову человека. Гарды в сеть не ходят вовсе, и заведение — не исключение:\n * дерево заводят один раз, руками.\n *\n * Сеть здесь отделена от решения так же, как у отправки груза: чем сходить, приходит доводом, и\n * спека подставляет двойник, никуда не стучась.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { IEnrollGranted } from './cargo.js';\nimport { intakeUrl, SHIP_TIMEOUT_MS } from './ship.js';\n\n/** Права файла с токеном: читает и пишет только владелец файла. */\nconst TOKEN_MODE: number = 0o600;\n\n/** Тело обращения: признак дерева и код приглашения. Имени в нём нет — его знает приглашение. */\nexport interface IEnrollBody {\n readonly schema: string;\n readonly tree: string;\n readonly code: string;\n}\n\n/** Чем кончилось обращение. Отказ — такой же ответ, как выданный токен: его печатает вызывающий. */\nexport interface IEnrollAnswer {\n readonly ok: boolean;\n /** Код ответа. Ноль — приём не ответил вовсе: не дозвонились, оборвалось, вышло время. */\n readonly status: number;\n readonly said: string;\n /** Токен и имя дерева. Пусто у отказа: приём называет их только у годного обращения. */\n readonly granted: IEnrollGranted | null;\n}\n\n/** Чем обращение уходит в приём. Двойник в спеке — того же вида. */\nexport type TEnrollCall = (intake: string, body: IEnrollBody) => Promise<IEnrollAnswer>;\n\n/** Что нужно заведению: где дерево, куда идти, чем платить и куда класть токен. */\nexport interface IEnrollOptions {\n readonly root: string;\n readonly intake: string;\n readonly code: string;\n /**\n * Токен, выданный человеку прямо в админке приёма.\n *\n * Второй путь завести дерево, и сети он не требует вовсе: токен уже на руках, обменивать не\n * на что. С кодом приглашения не сочетается — это два пути к одному и тому же.\n */\n readonly issued: string;\n /** Признак дерева, посчитанный от адреса репозитория. */\n readonly tree: string;\n /** Файл токена — путь от корня дерева или от домашнего каталога. */\n readonly token: string;\n /** Перезаписать лежащий токен намеренно. Без него дерево с токеном обращения не шлёт. */\n readonly force: boolean;\n readonly call: TEnrollCall;\n}\n\n/** Что команда напечатала и чем кончилась. Строки печатает вызывающий. */\nexport interface IEnrollOutcome {\n readonly code: number;\n readonly lines: readonly string[];\n}\n\n/** Отказ команды: причина строками и ненулевой код выхода. */\nfunction refusal(...lines: readonly string[]): IEnrollOutcome {\n return { code: 1, lines };\n}\n\n/** Что сказал приём: его собственное сообщение, а если тело не разбирается — тело как приехало. */\nfunction saidOf(text: string): string {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['message'] === 'string' ? said['message'] : text.trim();\n } catch {\n return text.trim();\n }\n}\n\n/** Токен и имя из ответа приёма. Ответ не тем — пусто: своё устройство приём не пересказывает. */\nfunction grantedOf(text: string): IEnrollGranted | null {\n try {\n const said: Record<string, unknown> = JSON.parse(text) as Record<string, unknown>;\n\n return typeof said['token'] === 'string' && typeof said['tree'] === 'string' && typeof said['name'] === 'string'\n ? { tree: said['tree'], name: said['name'], token: said['token'] }\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Годен ли адрес приёма для обращения без TLS.\n *\n * Токен уходит единственным ответом на это обращение, и по открытому пути его читает всякий, кто\n * стоит между сторонами. Исключение одно — локальная машина: там приём поднимают для проверки, и\n * сети между сторонами нет вовсе.\n */\nexport function intakeAllowed(intake: string): boolean {\n const address: string = intake.trim().toLowerCase();\n\n if (address.startsWith('https://')) {\n return true;\n }\n\n return /^http:\\/\\/(localhost|127\\.0\\.0\\.1|\\[::1\\])([:/]|$)/.test(address);\n}\n\n/** Куда ложится токен: путь от домашнего каталога или от корня дерева. */\nexport function tokenPath(root: string, spoken: string): string {\n return spoken.startsWith('~/') ? join(homedir(), spoken.slice(2)) : join(root, spoken);\n}\n\n/** Запись токена на диск: у обоих путей она одна, и права файла у неё одни. */\nfunction lay(path: string, token: string): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${token}\\n`, { encoding: 'utf8', mode: TOKEN_MODE });\n}\n\n/** Токен с рук: приём о нём не спрашивается — он же его и выдал, и обменивать не на что. */\nfunction accept(options: IEnrollOptions, path: string): IEnrollOutcome {\n lay(path, options.issued);\n\n return {\n code: 0,\n lines: [\n 'дерево заведено токеном, выданным в админке приёма',\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'проверить связь с приёмом: `agent-kit propose --dry-run`',\n ],\n };\n}\n\n/** Обращение и запись токена: сюда доходит только то, что прошло проверки у себя. */\nasync function grant(options: IEnrollOptions, path: string): Promise<IEnrollOutcome> {\n const answer: IEnrollAnswer = await options.call(options.intake, { schema: '1', tree: options.tree, code: options.code });\n\n if (!answer.ok || !answer.granted) {\n return refusal(answer.status === 0 ? `приём не ответил: ${answer.said}` : `приём отказал (${answer.status}): ${answer.said}`);\n }\n\n lay(path, answer.granted.token);\n\n return {\n code: 0,\n lines: [\n `дерево заведено: «${answer.granted.name}» (${answer.granted.tree})`,\n `токен лежит в ${path}, читать и писать его может только владелец файла`,\n 'приглашение погашено: второй раз им не воспользоваться',\n ],\n };\n}\n\n/**\n * Выбран ли путь: отказ, когда доводов два или ни одного, и пусто, когда выбран ровно один.\n *\n * Стоит отдельно от самого заведения: разбор довода и порядок проверок — два разных решения, и\n * вместе они не читаются.\n */\nfunction pathChosen(options: IEnrollOptions): IEnrollOutcome | null {\n if (options.code && options.issued) {\n return refusal(\n 'код приглашения и выданный токен — два разных пути завести дерево, и вместе они не идут',\n 'оставьте один довод: --code <код> либо --token <токен>'\n );\n }\n\n if (!options.code && !options.issued) {\n return refusal(\n 'заведение ждёт одно из двух:',\n ' --code <код> код приглашения, выданный владельцем приёма',\n ' --token <токен> токен, выданный человеку прямо в админке приёма'\n );\n }\n\n return null;\n}\n\n/** Годен ли приём для обмена по коду. Пути с рук эти проверки не касаются: он в сеть не идёт. */\nfunction intakeReady(options: IEnrollOptions): IEnrollOutcome | null {\n if (!options.intake) {\n return refusal(\n 'в настройке дерева не назван адрес приёма: заполните ключ `intake`',\n 'адрес берётся у того же человека, который выдал код приглашения: код и адрес идут парой'\n );\n }\n\n if (!intakeAllowed(options.intake)) {\n return refusal(\n `адрес приёма ${options.intake} без TLS: токен уходит единственным ответом, и по открытому пути его прочитает всякий`,\n 'поправьте ключ `intake` на https://… — исключение сделано только для локальной машины'\n );\n }\n\n return null;\n}\n\n/**\n * Заведение дерева.\n *\n * Порядок проверок такой, что до сети доходит только то, что имеет шанс: незаполненная настройка,\n * открытый адрес и уже лежащий токен отбиваются здесь, у себя. Общие обоим путям проверки стоят\n * до развилки: поставленные после, они защищали бы только один из них.\n */\nexport async function enroll(options: IEnrollOptions): Promise<IEnrollOutcome> {\n const chosen: IEnrollOutcome | null = pathChosen(options);\n\n if (chosen) {\n return chosen;\n }\n\n if (!options.token) {\n return refusal('в настройке дерева не назван файл токена: заполните ключ `token`');\n }\n\n const laid: string = tokenPath(options.root, options.token);\n\n if (existsSync(laid) && !options.force) {\n return refusal(\n `токен уже лежит в ${laid}: заведённое дерево потеряло бы связь со своим прежним грузом`,\n 'если это намеренно — позовите ту же команду с доводом --force'\n );\n }\n\n if (options.issued) {\n return accept(options, laid);\n }\n\n const reachable: IEnrollOutcome | null = intakeReady(options);\n\n return reachable ?? grant(options, laid);\n}\n\n/** Обращение в приём по-настоящему. Код уезжает телом и в заголовки не попадает. */\nexport const httpEnroll: TEnrollCall = async (intake: string, body: IEnrollBody): Promise<IEnrollAnswer> => {\n let answer: Response;\n\n try {\n answer = await fetch(intakeUrl(intake, 'enroll'), {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(SHIP_TIMEOUT_MS),\n });\n } catch (error: unknown) {\n return { ok: false, status: 0, said: (error as Error).message, granted: null };\n }\n\n const text: string = await answer.text();\n\n return { ok: answer.ok, status: answer.status, said: saidOf(text), granted: answer.ok ? grantedOf(text) : null };\n};\n"]}
@@ -5,7 +5,7 @@ export declare const KEEP_DAYS: number;
5
5
  /** За сколько дней сводка отвечает, когда отрезок не назван. */
6
6
  export declare const DEFAULT_DAYS: number;
7
7
  /** Роды событий, которые пишут гарды. */
8
- export type TEvent = 'skill-load' | 'gate-deny' | 'guard-deny';
8
+ export type TEvent = 'skill-load' | 'gate-deny' | 'guard-deny' | 'push-gate';
9
9
  export interface IObservation {
10
10
  readonly event: string;
11
11
  /** Имя правила или гарда — имя ресурса пакета, и только оно. */
@@ -60,6 +60,15 @@ export interface ISummary {
60
60
  */
61
61
  readonly denialsOffFile: number;
62
62
  readonly guards: readonly ICount[];
63
+ /**
64
+ * Исходы гейта пуша: `green` — набор прогнан и зелёный, `red` — набор красный, `no-checks` —
65
+ * проверок в дереве не нашлось.
66
+ *
67
+ * Отбои гарда считаются строкой выше и отвечают на один вопрос из трёх. Гейт, у которого
68
+ * набор не нашёлся ни разу, в отбоях выглядит как гейт, у которого всё зелено: и тот и
69
+ * другой молчат. Разводит их только этот счёт.
70
+ */
71
+ readonly pushGate: readonly ICount[];
63
72
  /**
64
73
  * Гарды, разложенные в дерево и не отбившие за отрезок ни разу.
65
74
  *
@@ -1 +1 @@
1
- {"version":3,"file":"observations.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/observations.ts"],"names":[],"mappings":"AAgBA,gEAAgE;AAChE,eAAO,MAAM,gBAAgB,EAAE,MAAsC,CAAC;AAEtE,oGAAoG;AACpG,eAAO,MAAM,SAAS,EAAE,MAAW,CAAC;AAEpC,gEAAgE;AAChE,eAAO,MAAM,YAAY,EAAE,MAAU,CAAC;AAEtC,yCAAyC;AACzC,MAAM,MAAM,MAAM,GAAG,YAAY,GAAG,WAAW,GAAG,YAAY,CAAC;AAE/D,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC5B;AAED,qFAAqF;AACrF,MAAM,WAAW,MAAM;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAExD,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iCAAiC;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAOD,gGAAgG;AAChG,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAoBlE;AAED,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,YAAY,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/C,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,qDAAqD;IACrD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC5B;AAsBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CA8BvF;AAuBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CACrB,YAAY,EAAE,SAAS,YAAY,EAAE,EACrC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,QAAa,EACtB,MAAM,GAAE,SAAS,MAAM,EAAO,GAC/B,QAAQ,CAuBV"}
1
+ {"version":3,"file":"observations.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/observations.ts"],"names":[],"mappings":"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"}
@@ -148,6 +148,7 @@ export function summarize(observations, known, days, weights = {}, guards = [])
148
148
  kinds: countBy(denials.map((entry) => entry.kind)),
149
149
  denialsOffFile: denials.filter((entry) => OFF_FILE_KINDS.includes(entry.kind)).length,
150
150
  guards: countBy(of(observations, 'guard-deny').map((entry) => entry.resource)),
151
+ pushGate: countBy(of(observations, 'push-gate').map((entry) => entry.resource)),
151
152
  silentGuards: guards.filter((name) => !denied.has(name)).sort(byText),
152
153
  unused: known.filter((name) => !loaded.has(name)).sort(byText),
153
154
  versions: [...new Set(observations.map((entry) => entry.version).filter(Boolean))].sort(byText),
@@ -1 +1 @@
1
- {"version":3,"file":"observations.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/observations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Наблюдения и сводка по ним.\n *\n * Наблюдения пишет `hooks/observe.sh` — по строке на событие, по файлу на день. Здесь они\n * читаются и сводятся: чем пользовались, чем не пользовались ни разу, обо что спотыкались.\n *\n * Разбор отделён от чтения диска намеренно: сводка — то, ради чего наблюдения ведутся, и\n * проверять её на подложенных файлах значило бы проверять файловую систему.\n *\n * Дата приходит доводом, а не берётся из часов: сводка за отрезок дней иначе не проверяется\n * спекой — вчерашний файл фикстуры завтра оказывается позавчерашним.\n */\nimport { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда `observe.sh` пишет наблюдения. Путь от корня дерева. */\nexport const OBSERVATIONS_DIR: string = '.claude/rt-kit/observations';\n\n/** Сколько дней наблюдения лежат. Каталог, который только растёт, через год перестают открывать. */\nexport const KEEP_DAYS: number = 30;\n\n/** За сколько дней сводка отвечает, когда отрезок не назван. */\nexport const DEFAULT_DAYS: number = 3;\n\n/** Роды событий, которые пишут гарды. */\nexport type TEvent = 'skill-load' | 'gate-deny' | 'guard-deny';\n\nexport interface IObservation {\n readonly event: string;\n /** Имя правила или гарда — имя ресурса пакета, и только оно. */\n readonly resource: string;\n /** Род правки: расширение, `command` или `browser`. Пути в наблюдении нет. */\n readonly kind: string;\n /** Признак сессии: считает заходы, имени сессии не выдаёт. */\n readonly session: string;\n readonly version: string;\n}\n\n/** Сколько раз что-то встретилось. Порядок — по убыванию, при равенстве по имени. */\nexport interface ICount {\n readonly name: string;\n readonly count: number;\n}\n\n/**\n * Вес ресурса на диске, байтами: имя правила в число.\n *\n * Приходит доводом, а не читается здесь, по той же причине, что и день: разбор отделён от диска,\n * и сводку, читающую каталог правил сама, пришлось бы проверять подложенными файлами.\n */\nexport type TWeights = Readonly<Record<string, number>>;\n\nexport interface ISummary {\n readonly days: number;\n /** Сколько заходов оставило хоть одно наблюдение. */\n readonly sessions: number;\n readonly loads: readonly ICount[];\n /**\n * Вес того, что заходы прочитали за отрезок, байтами. Каждая загрузка считается своим весом:\n * правило, открытое в трёх заходах, стоило места трижды — тем, что оно одно, это не дешевле.\n * Ресурс, веса которого не назвали, считается нулём: врать числом хуже, чем не знать его.\n */\n readonly bytes: number;\n /**\n * Тот же вес, приходящийся на один заход, — цена входа в работу.\n *\n * Ноль заходов даёт ноль, а не отказ: сводка за пустой отрезок печатается наравне с полной,\n * и деление на ноль уронило бы её на том единственном случае, ради которого её и зовут, —\n * «а было ли вообще что-нибудь».\n */\n readonly bytesPerSession: number;\n /** Отбития гейта по правилам. */\n readonly denials: readonly ICount[];\n /** Род правки, на котором гейт отбивал. */\n readonly kinds: readonly ICount[];\n /**\n * Отбития, пришедшие не на правку файла: род правки — команда оболочки или браузер.\n *\n * Считается здесь, а не выводится читателем из разбивки по родам: доля, которую надо\n * сложить глазами из десятка строк, не читается вовсе — а именно она говорит, требует ли\n * гейт правило под то, что правкой не является.\n */\n readonly denialsOffFile: number;\n readonly guards: readonly ICount[];\n /**\n * Гарды, разложенные в дерево и не отбившие за отрезок ни разу.\n *\n * Считается той же разностью, что и незагруженные правила, и по той же причине: в самих\n * наблюдениях молчащего гарда нет по определению, а из перечня защит его никто не вычеркнет\n * — молчащий гард и ненужный выглядят одинаково ровно до разбора.\n */\n readonly silentGuards: readonly string[];\n /**\n * Правила, разложенные в дерево и не загруженные за отрезок ни разу. Мёртвый ресурс иначе\n * неотличим от работающего — и это единственная строка сводки, которую нельзя получить из\n * самих наблюдений.\n */\n readonly unused: readonly string[];\n readonly versions: readonly string[];\n readonly total: number;\n}\n\n/** Дата файла наблюдений: `2026-08-12.jsonl`. Чужое имя в каталоге не наблюдение. */\nconst DAY_FILE: RegExp = /^(\\d{4}-\\d{2}-\\d{2})\\.jsonl$/;\n\nconst dayOf: (name: string) => string | null = (name: string): string | null => DAY_FILE.exec(name)?.[1] ?? null;\n\n/** День, отстоящий от названного на столько-то суток назад. Считается в UTC — как и пишется. */\nexport function dayBefore(today: string, days: number): string {\n const at: number = Date.parse(`${today}T00:00:00Z`);\n\n return Number.isNaN(at) ? today : new Date(at - days * 86_400_000).toISOString().slice(0, 10);\n}\n\n/**\n * Одна строка наблюдения. Битая пропускается молча: наблюдение — побочная запись гарда, и\n * оборванная на полуслове строка не имеет права уронить сводку.\n */\nexport function parseObservation(line: string): IObservation | null {\n if (!line.trim()) {\n return null;\n }\n try {\n const found: Record<string, unknown> = JSON.parse(line) as Record<string, unknown>;\n const event: unknown = found['ev'];\n\n return typeof event === 'string' && event\n ? {\n event,\n resource: typeof found['res'] === 'string' ? found['res'] : '',\n kind: typeof found['kind'] === 'string' ? found['kind'] : '',\n session: typeof found['sid'] === 'string' ? found['sid'] : '',\n version: typeof found['v'] === 'string' ? found['v'] : '',\n }\n : null;\n } catch {\n return null;\n }\n}\n\nexport interface IReadResult {\n readonly observations: readonly IObservation[];\n /** Файлы, снятые по сроку хранения: их называют вслух, а не убирают молча. */\n readonly swept: readonly string[];\n /** Каталога нет вовсе — записи не велось ни разу. */\n readonly silent: boolean;\n}\n\n/** Раньше ли день черты. Дни записаны `ГГГГ-ММ-ДД`, и порядок строк у них хронологический. */\nfunction isBefore(day: string, edge: string): boolean {\n return byText(day, edge) < 0;\n}\n\n/** Записи одного дня из его файла. Строка, которая не разбирается, пропускается молча. */\nfunction observationsOfDay(path: string): readonly IObservation[] {\n const found: IObservation[] = [];\n\n for (const line of readFileSync(path, 'utf8').split('\\n')) {\n const one: IObservation | null = parseObservation(line);\n\n if (one) {\n found.push(one);\n }\n }\n\n return found;\n}\n\n/**\n * Наблюдения за отрезок и уборка того, что старше срока хранения.\n *\n * Уборка идёт здесь, а не отдельной командой: каталог растёт от работы, а не от запусков, и\n * команда, о которой надо помнить, до второго месяца не доживает.\n */\nexport function readObservations(root: string, today: string, days: number): IReadResult {\n const dir: string = join(root, OBSERVATIONS_DIR);\n if (!existsSync(dir)) {\n return { observations: [], swept: [], silent: true };\n }\n\n const since: string = dayBefore(today, days - 1);\n const keepFrom: string = dayBefore(today, KEEP_DAYS);\n const observations: IObservation[] = [];\n const swept: string[] = [];\n\n for (const name of readdirSync(dir).sort(byText)) {\n const day: string | null = dayOf(name);\n\n if (!day) {\n continue;\n }\n\n if (isBefore(day, keepFrom)) {\n rmSync(join(dir, name), { force: true });\n swept.push(name);\n } else if (isBefore(day, since)) {\n // День старше запрошенного отрезка, но моложе черты хранения: файл остаётся лежать,\n // а в этот отчёт не идёт\n } else {\n observations.push(...observationsOfDay(join(dir, name)));\n }\n }\n\n return { observations, swept, silent: false };\n}\n\nconst countBy: (values: readonly string[]) => readonly ICount[] = (values: readonly string[]): readonly ICount[] => {\n const counted: Map<string, number> = new Map();\n for (const value of values) {\n if (value) {\n counted.set(value, (counted.get(value) ?? 0) + 1);\n }\n }\n\n return [...counted]\n .map(([name, count]: [string, number]): ICount => ({ name, count }))\n .sort((left: ICount, right: ICount): number => right.count - left.count || left.name.localeCompare(right.name));\n};\n\nconst of: (observations: readonly IObservation[], event: TEvent) => readonly IObservation[] = (\n observations: readonly IObservation[],\n event: TEvent\n): readonly IObservation[] => observations.filter((entry: IObservation): boolean => entry.event === event);\n\n/** Роды правки, которые правкой файла не являются: гейт сработал не на файле. */\nconst OFF_FILE_KINDS: readonly string[] = ['command', 'browser'];\n\n/**\n * Сводка за отрезок.\n *\n * `known` — имена правил, разложенных в это дерево. Без них сводка отвечает только на вопрос\n * «чем пользовались», а самое ценное — чем не пользовались ни разу — сказать нечем: в самих\n * наблюдениях незагруженного правила нет по определению.\n *\n * `guards` — имена гардов, разложенных в это дерево. Гардом здесь считается тот, кто заявил своё\n * имя для наблюдений: хук без заявки в наблюдениях не появится ни при каком отрезке, и молчащим\n * он назывался бы неверно — он молчит не потому, что не понадобился.\n *\n * `weights` — вес этих правил на диске. Без них сводка говорит, сколько раз правило открывали, и\n * молчит о том, во что это обошлось: тридцать загрузок паттерна и тридцать загрузок правила на\n * пятьсот строк стоят разного, а в счёте выглядят одинаково. Не назван — вес считается нулём, и\n * строки о нём в выводе не будет вовсе.\n */\nexport function summarize(\n observations: readonly IObservation[],\n known: readonly string[],\n days: number,\n weights: TWeights = {},\n guards: readonly string[] = []\n): ISummary {\n const loads: readonly IObservation[] = of(observations, 'skill-load');\n const denials: readonly IObservation[] = of(observations, 'gate-deny');\n const loaded: Set<string> = new Set(loads.map((entry: IObservation): string => entry.resource));\n const sessions: number = new Set(observations.map((entry: IObservation): string => entry.session).filter(Boolean)).size;\n const bytes: number = loads.reduce((found: number, entry: IObservation): number => found + (weights[entry.resource] ?? 0), 0);\n const denied: Set<string> = new Set(of(observations, 'guard-deny').map((entry: IObservation): string => entry.resource));\n\n return {\n days,\n sessions,\n bytes,\n loads: countBy(loads.map((entry: IObservation): string => entry.resource)),\n bytesPerSession: sessions > 0 ? Math.round(bytes / sessions) : 0,\n denials: countBy(denials.map((entry: IObservation): string => entry.resource)),\n kinds: countBy(denials.map((entry: IObservation): string => entry.kind)),\n denialsOffFile: denials.filter((entry: IObservation): boolean => OFF_FILE_KINDS.includes(entry.kind)).length,\n guards: countBy(of(observations, 'guard-deny').map((entry: IObservation): string => entry.resource)),\n silentGuards: guards.filter((name: string): boolean => !denied.has(name)).sort(byText),\n unused: known.filter((name: string): boolean => !loaded.has(name)).sort(byText),\n versions: [...new Set(observations.map((entry: IObservation): string => entry.version).filter(Boolean))].sort(byText),\n total: observations.length,\n };\n}\n"]}
1
+ {"version":3,"file":"observations.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/observations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Наблюдения и сводка по ним.\n *\n * Наблюдения пишет `hooks/observe.sh` — по строке на событие, по файлу на день. Здесь они\n * читаются и сводятся: чем пользовались, чем не пользовались ни разу, обо что спотыкались.\n *\n * Разбор отделён от чтения диска намеренно: сводка — то, ради чего наблюдения ведутся, и\n * проверять её на подложенных файлах значило бы проверять файловую систему.\n *\n * Дата приходит доводом, а не берётся из часов: сводка за отрезок дней иначе не проверяется\n * спекой — вчерашний файл фикстуры завтра оказывается позавчерашним.\n */\nimport { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда `observe.sh` пишет наблюдения. Путь от корня дерева. */\nexport const OBSERVATIONS_DIR: string = '.claude/rt-kit/observations';\n\n/** Сколько дней наблюдения лежат. Каталог, который только растёт, через год перестают открывать. */\nexport const KEEP_DAYS: number = 30;\n\n/** За сколько дней сводка отвечает, когда отрезок не назван. */\nexport const DEFAULT_DAYS: number = 3;\n\n/** Роды событий, которые пишут гарды. */\nexport type TEvent = 'skill-load' | 'gate-deny' | 'guard-deny' | 'push-gate';\n\nexport interface IObservation {\n readonly event: string;\n /** Имя правила или гарда — имя ресурса пакета, и только оно. */\n readonly resource: string;\n /** Род правки: расширение, `command` или `browser`. Пути в наблюдении нет. */\n readonly kind: string;\n /** Признак сессии: считает заходы, имени сессии не выдаёт. */\n readonly session: string;\n readonly version: string;\n}\n\n/** Сколько раз что-то встретилось. Порядок — по убыванию, при равенстве по имени. */\nexport interface ICount {\n readonly name: string;\n readonly count: number;\n}\n\n/**\n * Вес ресурса на диске, байтами: имя правила в число.\n *\n * Приходит доводом, а не читается здесь, по той же причине, что и день: разбор отделён от диска,\n * и сводку, читающую каталог правил сама, пришлось бы проверять подложенными файлами.\n */\nexport type TWeights = Readonly<Record<string, number>>;\n\nexport interface ISummary {\n readonly days: number;\n /** Сколько заходов оставило хоть одно наблюдение. */\n readonly sessions: number;\n readonly loads: readonly ICount[];\n /**\n * Вес того, что заходы прочитали за отрезок, байтами. Каждая загрузка считается своим весом:\n * правило, открытое в трёх заходах, стоило места трижды — тем, что оно одно, это не дешевле.\n * Ресурс, веса которого не назвали, считается нулём: врать числом хуже, чем не знать его.\n */\n readonly bytes: number;\n /**\n * Тот же вес, приходящийся на один заход, — цена входа в работу.\n *\n * Ноль заходов даёт ноль, а не отказ: сводка за пустой отрезок печатается наравне с полной,\n * и деление на ноль уронило бы её на том единственном случае, ради которого её и зовут, —\n * «а было ли вообще что-нибудь».\n */\n readonly bytesPerSession: number;\n /** Отбития гейта по правилам. */\n readonly denials: readonly ICount[];\n /** Род правки, на котором гейт отбивал. */\n readonly kinds: readonly ICount[];\n /**\n * Отбития, пришедшие не на правку файла: род правки — команда оболочки или браузер.\n *\n * Считается здесь, а не выводится читателем из разбивки по родам: доля, которую надо\n * сложить глазами из десятка строк, не читается вовсе — а именно она говорит, требует ли\n * гейт правило под то, что правкой не является.\n */\n readonly denialsOffFile: number;\n readonly guards: readonly ICount[];\n /**\n * Исходы гейта пуша: `green` — набор прогнан и зелёный, `red` — набор красный, `no-checks` —\n * проверок в дереве не нашлось.\n *\n * Отбои гарда считаются строкой выше и отвечают на один вопрос из трёх. Гейт, у которого\n * набор не нашёлся ни разу, в отбоях выглядит как гейт, у которого всё зелено: и тот и\n * другой молчат. Разводит их только этот счёт.\n */\n readonly pushGate: readonly ICount[];\n /**\n * Гарды, разложенные в дерево и не отбившие за отрезок ни разу.\n *\n * Считается той же разностью, что и незагруженные правила, и по той же причине: в самих\n * наблюдениях молчащего гарда нет по определению, а из перечня защит его никто не вычеркнет\n * — молчащий гард и ненужный выглядят одинаково ровно до разбора.\n */\n readonly silentGuards: readonly string[];\n /**\n * Правила, разложенные в дерево и не загруженные за отрезок ни разу. Мёртвый ресурс иначе\n * неотличим от работающего — и это единственная строка сводки, которую нельзя получить из\n * самих наблюдений.\n */\n readonly unused: readonly string[];\n readonly versions: readonly string[];\n readonly total: number;\n}\n\n/** Дата файла наблюдений: `2026-08-12.jsonl`. Чужое имя в каталоге не наблюдение. */\nconst DAY_FILE: RegExp = /^(\\d{4}-\\d{2}-\\d{2})\\.jsonl$/;\n\nconst dayOf: (name: string) => string | null = (name: string): string | null => DAY_FILE.exec(name)?.[1] ?? null;\n\n/** День, отстоящий от названного на столько-то суток назад. Считается в UTC — как и пишется. */\nexport function dayBefore(today: string, days: number): string {\n const at: number = Date.parse(`${today}T00:00:00Z`);\n\n return Number.isNaN(at) ? today : new Date(at - days * 86_400_000).toISOString().slice(0, 10);\n}\n\n/**\n * Одна строка наблюдения. Битая пропускается молча: наблюдение — побочная запись гарда, и\n * оборванная на полуслове строка не имеет права уронить сводку.\n */\nexport function parseObservation(line: string): IObservation | null {\n if (!line.trim()) {\n return null;\n }\n try {\n const found: Record<string, unknown> = JSON.parse(line) as Record<string, unknown>;\n const event: unknown = found['ev'];\n\n return typeof event === 'string' && event\n ? {\n event,\n resource: typeof found['res'] === 'string' ? found['res'] : '',\n kind: typeof found['kind'] === 'string' ? found['kind'] : '',\n session: typeof found['sid'] === 'string' ? found['sid'] : '',\n version: typeof found['v'] === 'string' ? found['v'] : '',\n }\n : null;\n } catch {\n return null;\n }\n}\n\nexport interface IReadResult {\n readonly observations: readonly IObservation[];\n /** Файлы, снятые по сроку хранения: их называют вслух, а не убирают молча. */\n readonly swept: readonly string[];\n /** Каталога нет вовсе — записи не велось ни разу. */\n readonly silent: boolean;\n}\n\n/** Раньше ли день черты. Дни записаны `ГГГГ-ММ-ДД`, и порядок строк у них хронологический. */\nfunction isBefore(day: string, edge: string): boolean {\n return byText(day, edge) < 0;\n}\n\n/** Записи одного дня из его файла. Строка, которая не разбирается, пропускается молча. */\nfunction observationsOfDay(path: string): readonly IObservation[] {\n const found: IObservation[] = [];\n\n for (const line of readFileSync(path, 'utf8').split('\\n')) {\n const one: IObservation | null = parseObservation(line);\n\n if (one) {\n found.push(one);\n }\n }\n\n return found;\n}\n\n/**\n * Наблюдения за отрезок и уборка того, что старше срока хранения.\n *\n * Уборка идёт здесь, а не отдельной командой: каталог растёт от работы, а не от запусков, и\n * команда, о которой надо помнить, до второго месяца не доживает.\n */\nexport function readObservations(root: string, today: string, days: number): IReadResult {\n const dir: string = join(root, OBSERVATIONS_DIR);\n if (!existsSync(dir)) {\n return { observations: [], swept: [], silent: true };\n }\n\n const since: string = dayBefore(today, days - 1);\n const keepFrom: string = dayBefore(today, KEEP_DAYS);\n const observations: IObservation[] = [];\n const swept: string[] = [];\n\n for (const name of readdirSync(dir).sort(byText)) {\n const day: string | null = dayOf(name);\n\n if (!day) {\n continue;\n }\n\n if (isBefore(day, keepFrom)) {\n rmSync(join(dir, name), { force: true });\n swept.push(name);\n } else if (isBefore(day, since)) {\n // День старше запрошенного отрезка, но моложе черты хранения: файл остаётся лежать,\n // а в этот отчёт не идёт\n } else {\n observations.push(...observationsOfDay(join(dir, name)));\n }\n }\n\n return { observations, swept, silent: false };\n}\n\nconst countBy: (values: readonly string[]) => readonly ICount[] = (values: readonly string[]): readonly ICount[] => {\n const counted: Map<string, number> = new Map();\n for (const value of values) {\n if (value) {\n counted.set(value, (counted.get(value) ?? 0) + 1);\n }\n }\n\n return [...counted]\n .map(([name, count]: [string, number]): ICount => ({ name, count }))\n .sort((left: ICount, right: ICount): number => right.count - left.count || left.name.localeCompare(right.name));\n};\n\nconst of: (observations: readonly IObservation[], event: TEvent) => readonly IObservation[] = (\n observations: readonly IObservation[],\n event: TEvent\n): readonly IObservation[] => observations.filter((entry: IObservation): boolean => entry.event === event);\n\n/** Роды правки, которые правкой файла не являются: гейт сработал не на файле. */\nconst OFF_FILE_KINDS: readonly string[] = ['command', 'browser'];\n\n/**\n * Сводка за отрезок.\n *\n * `known` — имена правил, разложенных в это дерево. Без них сводка отвечает только на вопрос\n * «чем пользовались», а самое ценное — чем не пользовались ни разу — сказать нечем: в самих\n * наблюдениях незагруженного правила нет по определению.\n *\n * `guards` — имена гардов, разложенных в это дерево. Гардом здесь считается тот, кто заявил своё\n * имя для наблюдений: хук без заявки в наблюдениях не появится ни при каком отрезке, и молчащим\n * он назывался бы неверно — он молчит не потому, что не понадобился.\n *\n * `weights` — вес этих правил на диске. Без них сводка говорит, сколько раз правило открывали, и\n * молчит о том, во что это обошлось: тридцать загрузок паттерна и тридцать загрузок правила на\n * пятьсот строк стоят разного, а в счёте выглядят одинаково. Не назван — вес считается нулём, и\n * строки о нём в выводе не будет вовсе.\n */\nexport function summarize(\n observations: readonly IObservation[],\n known: readonly string[],\n days: number,\n weights: TWeights = {},\n guards: readonly string[] = []\n): ISummary {\n const loads: readonly IObservation[] = of(observations, 'skill-load');\n const denials: readonly IObservation[] = of(observations, 'gate-deny');\n const loaded: Set<string> = new Set(loads.map((entry: IObservation): string => entry.resource));\n const sessions: number = new Set(observations.map((entry: IObservation): string => entry.session).filter(Boolean)).size;\n const bytes: number = loads.reduce((found: number, entry: IObservation): number => found + (weights[entry.resource] ?? 0), 0);\n const denied: Set<string> = new Set(of(observations, 'guard-deny').map((entry: IObservation): string => entry.resource));\n\n return {\n days,\n sessions,\n bytes,\n loads: countBy(loads.map((entry: IObservation): string => entry.resource)),\n bytesPerSession: sessions > 0 ? Math.round(bytes / sessions) : 0,\n denials: countBy(denials.map((entry: IObservation): string => entry.resource)),\n kinds: countBy(denials.map((entry: IObservation): string => entry.kind)),\n denialsOffFile: denials.filter((entry: IObservation): boolean => OFF_FILE_KINDS.includes(entry.kind)).length,\n guards: countBy(of(observations, 'guard-deny').map((entry: IObservation): string => entry.resource)),\n pushGate: countBy(of(observations, 'push-gate').map((entry: IObservation): string => entry.resource)),\n silentGuards: guards.filter((name: string): boolean => !denied.has(name)).sort(byText),\n unused: known.filter((name: string): boolean => !loaded.has(name)).sort(byText),\n versions: [...new Set(observations.map((entry: IObservation): string => entry.version).filter(Boolean))].sort(byText),\n total: observations.length,\n };\n}\n"]}
@@ -0,0 +1,24 @@
1
+ export interface IOverrideMark {
2
+ /** Файл надстройки от корня дерева. */
3
+ readonly file: string;
4
+ /** Заголовок раздела, который пометка держит. */
5
+ readonly heading: string;
6
+ /** Ресурс пакета, ради статьи которого раздел заведён. */
7
+ readonly resource: string;
8
+ /** Статья: та самая цитата, которую называло предложение. */
9
+ readonly article: string;
10
+ /** День отправки, `ГГГГ-ММ-ДД`. */
11
+ readonly day: string;
12
+ }
13
+ /** Пометки одного файла надстройки. Раздел без пометки считается постоянным. */
14
+ export declare function marksOfOverride(text: string, file: string): readonly IOverrideMark[];
15
+ /**
16
+ * Надстройки, чья статья в новой редакции пакета уже есть.
17
+ *
18
+ * `assetsDir` — каталог ресурсов той редакции, которая раскладывается сейчас: сравнение идёт с
19
+ * ней, а не с тем, что уже лежит в дереве. Ресурса в редакции нет — пометка молчит: пакет мог его
20
+ * переименовать, и называть надстройку лишней на этом основании значило бы советовать снять то,
21
+ * чему замена не пришла.
22
+ */
23
+ export declare function staleOverrides(root: string, assetsDir: string): readonly IOverrideMark[];
24
+ //# sourceMappingURL=override-marks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"override-marks.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/override-marks.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,aAAa;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mCAAmC;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAOD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAmBpF;AA0BD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAqBxF"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Пометки на надстройках: какой раздел заведён ради отправленного предложения и когда его можно
3
+ * снять.
4
+ *
5
+ * Надстройку заводят в день отправки предложения, а снимают, когда правка выходит в новой
6
+ * редакции пакета. Между этими днями проходит месяц, другой заход и другой повод: в минуту
7
+ * обновления у исполнителя в руках разложенный ресурс и надстройка, а записи предложений он не
8
+ * открывает — на них ничто не указывает. Надстройка остаётся замещать раздел, который пакет
9
+ * давно содержит сам.
10
+ *
11
+ * Пометка держит эту связь на самой надстройке: имя ресурса пакета, статья, ради которой раздел
12
+ * заведён, и день отправки. Форма — комментарий разметки: он не виден в собранном тексте,
13
+ * переживает слияние по заголовкам и читается машиной.
14
+ *
15
+ * Снимает раздел человек, а не команда: пометка называет одну статью, а в раздел могли дописать
16
+ * и другое — снятие вслепую удалило бы то, чего пометка не касалась.
17
+ */
18
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
19
+ import { join, relative } from 'node:path';
20
+ import { OVERRIDES_DIR } from './config.js';
21
+ import { parseDocument } from './sections.js';
22
+ import { byText } from './order.js';
23
+ /**
24
+ * Пометка раздела надстройки:
25
+ * `<!-- rt-proposed: rules/task-flow.md · «статья целиком» · 2026-09-03 -->`
26
+ *
27
+ * Разделители те же, что у шапки раскладки: строку читает человек, и второй знак разделения в
28
+ * тех же файлах читался бы как другая пометка.
29
+ */
30
+ const MARK = /<!-- rt-proposed:\s?([^·]+)·\s?«([^»]*)»\s?·\s?([0-9-]+) -->/;
31
+ /** Один пробел вместо любой последовательности пробелов: статья в разметке перенесена по ширине строки. */
32
+ function flat(text) {
33
+ return text.replace(/\s+/g, ' ').trim();
34
+ }
35
+ /** Пометки одного файла надстройки. Раздел без пометки считается постоянным. */
36
+ export function marksOfOverride(text, file) {
37
+ const document = parseDocument(text);
38
+ const found = [];
39
+ for (const section of document.sections) {
40
+ const match = MARK.exec(section.body);
41
+ if (match) {
42
+ found.push({
43
+ file,
44
+ heading: section.heading.replace(/^#+\s*/, ''),
45
+ resource: flat(match[1]),
46
+ article: flat(match[2]),
47
+ day: match[3].trim(),
48
+ });
49
+ }
50
+ }
51
+ return found;
52
+ }
53
+ /** Файлы каталога надстроек, путями от самого каталога. */
54
+ function overrideFiles(dir) {
55
+ if (!existsSync(dir)) {
56
+ return [];
57
+ }
58
+ const found = [];
59
+ const walk = (at) => {
60
+ for (const name of readdirSync(at).sort(byText)) {
61
+ const path = join(at, name);
62
+ if (statSync(path).isDirectory()) {
63
+ walk(path);
64
+ }
65
+ else {
66
+ found.push(path);
67
+ }
68
+ }
69
+ };
70
+ walk(dir);
71
+ return found;
72
+ }
73
+ /**
74
+ * Надстройки, чья статья в новой редакции пакета уже есть.
75
+ *
76
+ * `assetsDir` — каталог ресурсов той редакции, которая раскладывается сейчас: сравнение идёт с
77
+ * ней, а не с тем, что уже лежит в дереве. Ресурса в редакции нет — пометка молчит: пакет мог его
78
+ * переименовать, и называть надстройку лишней на этом основании значило бы советовать снять то,
79
+ * чему замена не пришла.
80
+ */
81
+ export function staleOverrides(root, assetsDir) {
82
+ const dir = join(root, OVERRIDES_DIR);
83
+ const stale = [];
84
+ for (const path of overrideFiles(dir)) {
85
+ const marks = marksOfOverride(readFileSync(path, 'utf8'), relative(root, path));
86
+ for (const mark of marks) {
87
+ const source = join(assetsDir, mark.resource);
88
+ if (!mark.article || !existsSync(source)) {
89
+ continue;
90
+ }
91
+ if (flat(readFileSync(source, 'utf8')).includes(mark.article)) {
92
+ stale.push(mark);
93
+ }
94
+ }
95
+ }
96
+ return stale;
97
+ }
98
+ //# sourceMappingURL=override-marks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"override-marks.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/override-marks.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Пометки на надстройках: какой раздел заведён ради отправленного предложения и когда его можно\n * снять.\n *\n * Надстройку заводят в день отправки предложения, а снимают, когда правка выходит в новой\n * редакции пакета. Между этими днями проходит месяц, другой заход и другой повод: в минуту\n * обновления у исполнителя в руках разложенный ресурс и надстройка, а записи предложений он не\n * открывает — на них ничто не указывает. Надстройка остаётся замещать раздел, который пакет\n * давно содержит сам.\n *\n * Пометка держит эту связь на самой надстройке: имя ресурса пакета, статья, ради которой раздел\n * заведён, и день отправки. Форма — комментарий разметки: он не виден в собранном тексте,\n * переживает слияние по заголовкам и читается машиной.\n *\n * Снимает раздел человек, а не команда: пометка называет одну статью, а в раздел могли дописать\n * и другое — снятие вслепую удалило бы то, чего пометка не касалась.\n */\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';\nimport { join, relative } from 'node:path';\n\nimport { OVERRIDES_DIR } from './config.js';\nimport { parseDocument, IDocument } from './sections.js';\nimport { byText } from './order.js';\n\n/**\n * Пометка раздела надстройки:\n * `<!-- rt-proposed: rules/task-flow.md · «статья целиком» · 2026-09-03 -->`\n *\n * Разделители те же, что у шапки раскладки: строку читает человек, и второй знак разделения в\n * тех же файлах читался бы как другая пометка.\n */\nconst MARK: RegExp = /<!-- rt-proposed:\\s?([^·]+)·\\s?«([^»]*)»\\s?·\\s?([0-9-]+) -->/;\n\nexport interface IOverrideMark {\n /** Файл надстройки от корня дерева. */\n readonly file: string;\n /** Заголовок раздела, который пометка держит. */\n readonly heading: string;\n /** Ресурс пакета, ради статьи которого раздел заведён. */\n readonly resource: string;\n /** Статья: та самая цитата, которую называло предложение. */\n readonly article: string;\n /** День отправки, `ГГГГ-ММ-ДД`. */\n readonly day: string;\n}\n\n/** Один пробел вместо любой последовательности пробелов: статья в разметке перенесена по ширине строки. */\nfunction flat(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n/** Пометки одного файла надстройки. Раздел без пометки считается постоянным. */\nexport function marksOfOverride(text: string, file: string): readonly IOverrideMark[] {\n const document: IDocument = parseDocument(text);\n const found: IOverrideMark[] = [];\n\n for (const section of document.sections) {\n const match: RegExpExecArray | null = MARK.exec(section.body);\n\n if (match) {\n found.push({\n file,\n heading: section.heading.replace(/^#+\\s*/, ''),\n resource: flat(match[1]),\n article: flat(match[2]),\n day: match[3].trim(),\n });\n }\n }\n\n return found;\n}\n\n/** Файлы каталога надстроек, путями от самого каталога. */\nfunction overrideFiles(dir: string): readonly string[] {\n if (!existsSync(dir)) {\n return [];\n }\n\n const found: string[] = [];\n const walk: (at: string) => void = (at: string): void => {\n for (const name of readdirSync(at).sort(byText)) {\n const path: string = join(at, name);\n\n if (statSync(path).isDirectory()) {\n walk(path);\n } else {\n found.push(path);\n }\n }\n };\n\n walk(dir);\n\n return found;\n}\n\n/**\n * Надстройки, чья статья в новой редакции пакета уже есть.\n *\n * `assetsDir` — каталог ресурсов той редакции, которая раскладывается сейчас: сравнение идёт с\n * ней, а не с тем, что уже лежит в дереве. Ресурса в редакции нет — пометка молчит: пакет мог его\n * переименовать, и называть надстройку лишней на этом основании значило бы советовать снять то,\n * чему замена не пришла.\n */\nexport function staleOverrides(root: string, assetsDir: string): readonly IOverrideMark[] {\n const dir: string = join(root, OVERRIDES_DIR);\n const stale: IOverrideMark[] = [];\n\n for (const path of overrideFiles(dir)) {\n const marks: readonly IOverrideMark[] = marksOfOverride(readFileSync(path, 'utf8'), relative(root, path));\n\n for (const mark of marks) {\n const source: string = join(assetsDir, mark.resource);\n\n if (!mark.article || !existsSync(source)) {\n continue;\n }\n\n if (flat(readFileSync(source, 'utf8')).includes(mark.article)) {\n stale.push(mark);\n }\n }\n }\n\n return stale;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"shipment.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"AAcA,OAAO,EAGH,cAAc,EACd,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,aAAa,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAe,QAAQ,EAA+B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAS,SAAS,EAAyE,MAAM,gBAAgB,CAAC;AACzH,OAAO,EAAE,SAAS,EAAuB,KAAK,EAAE,MAAM,WAAW,CAAC;AAalE,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjE;AAaD,uFAAuF;AACvF,wBAAgB,YAAY,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,aAAa,CAkBf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAe5E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,GAAG,SAAS,SAAS,EAAE,CAMpI;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWxH;AAsJD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA6GlG"}
1
+ {"version":3,"file":"shipment.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"AAcA,OAAO,EAGH,cAAc,EACd,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,aAAa,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAe,QAAQ,EAA+B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAS,SAAS,EAAyE,MAAM,gBAAgB,CAAC;AACzH,OAAO,EAAE,SAAS,EAAuB,KAAK,EAAE,MAAM,WAAW,CAAC;AAalE,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjE;AAaD,uFAAuF;AACvF,wBAAgB,YAAY,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,aAAa,CAkBf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAe5E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,GAAG,SAAS,SAAS,EAAE,CAMpI;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWxH;AAsJD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgHlG"}
package/lib/shipment.js CHANGED
@@ -250,7 +250,7 @@ export async function propose(env, options) {
250
250
  };
251
251
  }
252
252
  if (!config.intake) {
253
- return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \`intake\` в ${CONFIG_PATH}`);
253
+ return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \`intake\` в ${CONFIG_PATH}, а само значение берётся у владельца приёма — вместе с кодом приглашения`);
254
254
  }
255
255
  const tree = treeSlugOf(options.remote, config.tree);
256
256
  if (!tree) {
@@ -1 +1 @@
1
- {"version":3,"file":"shipment.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Сборка груза и команда отправки.\n *\n * Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без\n * замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево\n * переопределило. Отправка, построенная вокруг предложения, эти данные теряла.\n *\n * Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке\n * подменяется двойником.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport {\n CARGO_SCHEMA_VERSION,\n ICargoCount,\n ICargoOverride,\n IPostmortemItem,\n IPostmortemsCargo,\n IProposalItem,\n IProposalsCargo,\n ISummaryCargo,\n} from './cargo.js';\nimport { IEnvironment, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH, IConfig, readConfig } from './config.js';\nimport { IReadResult, ISummary, readObservations, summarize } from './observations.js';\nimport { ILeak, IProposal, leaksIn, markSent, marksOf, nearestMissing, readProposals, TO_PACKAGE } from './proposals.js';\nimport { IShipment, IShipped, readToken, TShip } from './ship.js';\nimport { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';\nimport { byText } from './order.js';\n\n/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */\nconst SLUG_LENGTH: number = 12;\n\n/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */\nconst REFUSED: number = 1;\n\n/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */\nconst TOKEN_REFUSED: number = 401;\n\n/** Чем отправка живёт: чем отправлять, чем себя выдаёт дерево и за какой отрезок сводка. */\nexport interface IShipOptions {\n /** Печатать, что уехало бы, и не отправлять ничего. */\n readonly dryRun: boolean;\n readonly ship: TShip;\n /** Адрес удалённого репозитория этого дерева: из него считается признак. */\n readonly remote: string;\n /** Имена удалённых репозиториев. Нужны отказу: выбор между несколькими делает человек. */\n readonly remotes?: readonly string[];\n /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */\n readonly today: string;\n readonly days: number;\n}\n\n/**\n * Адрес удалённого репозитория, приведённый к одному виду.\n *\n * Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они\n * ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а\n * непривёденные формы дали бы два.\n */\nexport function remoteMarkOf(remote: string): string {\n const named: string = remote\n .trim()\n .replace(/\\.git$/, '')\n .replace(/^[a-z+]+:\\/\\//i, '')\n .replace(/^[^@/]+@/, '')\n .replace(/:/g, '/');\n let end: number = named.length;\n\n while (end > 0 && named[end - 1] === '/') {\n end -= 1;\n }\n\n return named.slice(0, end).toLowerCase();\n}\n\n/**\n * Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.\n *\n * Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих\n * копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе\n * все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.\n */\nexport function treeSlugOf(remote: string, spoken: string): string {\n const mark: string = remoteMarkOf(remote);\n\n return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;\n}\n\n/**\n * Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и\n * наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.\n *\n * Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,\n * а не наблюдения.\n */\nfunction knownCounts(counts: readonly ICargoCount[], known: ReadonlySet<string>): readonly ICargoCount[] {\n return counts.filter((count: ICargoCount): boolean => known.has(count.name));\n}\n\n/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */\nexport function summaryCargo(\n summary: ISummary,\n tree: string,\n overrides: readonly ICargoOverride[],\n unpicked: readonly string[],\n known: ReadonlySet<string>\n): ISummaryCargo {\n return {\n tree,\n unpicked,\n overrides,\n schema: CARGO_SCHEMA_VERSION,\n days: summary.days,\n sessions: summary.sessions,\n loads: knownCounts(summary.loads, known),\n denials: knownCounts(summary.denials, known),\n // Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`\n // или `browser` — слова пакета, одинаковые у всех деревьев.\n kinds: summary.kinds,\n guards: knownCounts(summary.guards, known),\n unused: summary.unused,\n versions: summary.versions,\n total: summary.total,\n };\n}\n\n/**\n * Разборы происшествий, как они лежат на дереве.\n *\n * Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на\n * один вопрос, ради которого его читают.\n */\nexport function readPostmortems(root: string, dir: string): IPostmortemItem[] {\n const path: string = join(root, dir);\n\n if (!existsSync(path)) {\n return [];\n }\n\n return (\n readdirSync(path)\n // Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм\n // промаха, — и в приёме встало бы записью, которой нечего сказать.\n .filter((file: string): boolean => file.endsWith('.md') && file !== 'README.md')\n .sort(byText)\n .map((file: string): IPostmortemItem => ({ file, text: readFileSync(join(path, file), 'utf8') }))\n );\n}\n\n/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */\nexport function shipmentsOf(summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): readonly IShipment[] {\n return [\n { kind: 'сводка', operation: 'summary', body: summary },\n ...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),\n ...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),\n ];\n}\n\n/**\n * Что в грузе называет дерево, из которого он уезжает.\n *\n * Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству\n * называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его\n * отправку.\n */\nexport function leaksOfCargo(summary: ISummaryCargo, proposals: readonly IProposal[], marks: readonly string[]): string[] {\n const inSummary: string[] = leaksIn(JSON.stringify(summary, null, 1), marks).map(\n (leak: ILeak): string => ` сводка: ${leak.why}\\n ${leak.text}`\n );\n const inProposals: string[] = proposals.flatMap((entry: IProposal): string[] =>\n leaksIn(entry.body, marks).map(\n (leak: ILeak): string => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\\n ${leak.text}`\n )\n );\n\n return [...inSummary, ...inProposals];\n}\n\n/** Отказ строками. */\nfunction refusal(...lines: readonly string[]): IOutcomeOfCommand {\n return { code: REFUSED, lines: [...lines] };\n}\n\n/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */\nfunction describe(shipment: IShipment, summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): string {\n if (shipment.operation === 'proposals') {\n return `предложений ${proposals.items.length}`;\n }\n if (shipment.operation === 'postmortems') {\n return `разборов ${postmortems.items.length}`;\n }\n\n return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;\n}\n\n/** Блок, который не уехал, и причина этого. */\ninterface IRefusedProposal {\n readonly proposal: IProposal;\n readonly why: string;\n}\n\n/**\n * Отметка об отбое: отбитый блок остаётся на диске и несёт причину.\n *\n * Удалённый блок пишется следующим заходом заново — следа разбора нет, и повтор возвращается.\n * Отметка держит и то и другое: видно, что разбор был, и видно, почему он не стал правкой.\n */\nfunction markRefused(root: string, refused: readonly IRefusedProposal[]): void {\n for (const one of refused) {\n const path: string = join(root, one.proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), one.proposal, one.why, 'отбито'), 'utf8');\n }\n}\n\n/**\n * Перечень груза: что именно уедет и куда — строками, которые читаются до самой отправки.\n *\n * Печатается он обоими прогонами, и разделены они не окончанием глагола, а первой строкой:\n * «уехало» и «уехало бы» отличаются двумя буквами в хвосте, а строки под ними одинаковы до\n * знака. Прочитанный не тем прогоном, вывод сухого читается сделанной работой — и наоборот,\n * настоящая отправка читается пробой, после которой ничего не делают.\n */\nfunction manifest(\n going: readonly IShipment[],\n summary: ISummaryCargo,\n proposals: IProposalsCargo,\n postmortems: IPostmortemsCargo\n): readonly string[] {\n return going.map((shipment: IShipment): string => ` ${shipment.operation} — ${describe(shipment, summary, proposals, postmortems)}`);\n}\n\n/** Пометка об отправке: по ней предложение второй раз не уезжает. */\nfunction markProposals(root: string, proposals: readonly IProposal[], shipped: IShipped): void {\n const mark: string = `приём:${shipped.accepted?.month ?? 'принято'}`;\n\n for (const proposal of proposals) {\n const path: string = join(root, proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');\n }\n}\n\n/**\n * Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт\n * легшего и уже лежавшего.\n *\n * Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже\n * лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку\n * прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.\n */\nfunction accepted(shipped: IShipped): string {\n if (!shipped.accepted) {\n return 'принято';\n }\n\n const record: string = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;\n const added: number | undefined = shipped.accepted.added;\n\n return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;\n}\n\n/**\n * Отказ, когда признак дерева считать не из чего. Случая два, и говорят они разное: репозитория\n * нет вовсе — считать не из чего; репозиториев несколько и `origin` среди них нет — адрес есть, а\n * выбрать его может только человек. Угаданное неверно сливает в сводке приёма два дерева в одно,\n * и увидеть это нечем.\n */\nfunction noTreeMark(names: readonly string[]): IOutcomeOfCommand {\n if (names.length > 1) {\n return refusal(\n `признак дерева не считается: удалённых репозиториев несколько, а \\`origin\\` среди них нет — ${names.join(', ')}`,\n `переименуй тот, что называет это дерево, в \\`origin\\` — либо назови признак ключом \\`tree\\` в ${CONFIG_PATH}`\n );\n }\n\n return refusal(\n 'признак дерева не считается: удалённого репозитория нет',\n `назови его ключом \\`tree\\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`\n );\n}\n\n/**\n * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе\n * увозит половину и не оставляет следа, по которому видно, какую именно.\n */\nasync function send(\n root: string,\n intake: string,\n tree: string,\n token: string,\n ship: TShip,\n going: readonly IShipment[],\n proposals: readonly IProposal[],\n listed: readonly string[]\n): Promise<IOutcomeOfCommand> {\n // Что уедет, названо до того, как уехало: отказ на втором запросе иначе оставляет человека с\n // одной строкой о нём и без перечня, из которого видно, чего этот отказ стоил.\n const head: readonly string[] = [`ОТПРАВКА — груз уходит в ${intake}, дерево ${tree}:`, ...listed];\n const done: string[] = [];\n\n for (const shipment of going) {\n const shipped: IShipped = await ship(intake, token, shipment);\n\n if (!shipped.ok) {\n return {\n code: REFUSED,\n lines: [\n ...head,\n ...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),\n `${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,\n ...(shipped.status === TOKEN_REFUSED\n ? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']\n : []),\n ],\n };\n }\n\n done.push(` ${shipment.kind} → ${accepted(shipped)}`);\n\n if (shipment.operation === 'proposals') {\n markProposals(root, proposals, shipped);\n }\n }\n\n return { code: 0, lines: [...head, 'уехало:', ...done] };\n}\n\n/**\n * Отправка груза в приём.\n *\n * Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит\n * отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева\n * незачем.\n */\nexport async function propose(env: IEnvironment, options: IShipOptions): Promise<IOutcomeOfCommand> {\n const { root, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n\n if (!config) {\n return refusal(`конфига нет: ${CONFIG_PATH} — заводится \\`agent-kit init\\``);\n }\n if (!config.observe) {\n return {\n code: 0,\n lines: [\n 'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',\n 'выключатель гасит и отправку целиком, вместе со снимком надстроек',\n ],\n };\n }\n if (!config.intake) {\n return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \\`intake\\` в ${CONFIG_PATH}`);\n }\n\n const tree: string = treeSlugOf(options.remote, config.tree);\n if (!tree) {\n return noTreeMark(options.remotes ?? []);\n }\n\n const token: string = readToken(root, config.token);\n if (!token) {\n return refusal(\n 'токена дерева нет: груз остался неотправленным',\n `файл с ним называется ключом \\`token\\` в ${CONFIG_PATH} и лежит вне дерева`,\n 'выдаётся токен командой приёма — `tree:add` или `tree:token`'\n );\n }\n\n const read: IReadResult = readObservations(root, options.today, options.days);\n const summary: ISummary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);\n const cargo: ISummaryCargo = summaryCargo(\n summary,\n tree,\n treeSnapshot(config, assetsDir, root),\n unpickedOf(config, assetsDir),\n packagedNames(config, assetsDir)\n );\n\n const ready: readonly IProposal[] = readProposals(root).filter(\n (entry: IProposal): boolean => entry.address === TO_PACKAGE && !entry.sent\n );\n // Блок, не назвавший ближайшего утверждения ресурса, не уезжает: разбор, кончившийся ещё\n // одной статьёй о том, о чём статья уже стоит, снаружи неотличим от разбора, кончившегося\n // исправлением. Отбивается он поимённо, а остальные едут: один непрочитанный ресурс не\n // повод задержать чужую работу.\n const refused: readonly IRefusedProposal[] = ready\n .map((entry: IProposal): IRefusedProposal => ({ proposal: entry, why: nearestMissing(entry, assetsDir) }))\n .filter((entry: IRefusedProposal): boolean => Boolean(entry.why));\n const mine: readonly IProposal[] = ready.filter(\n (entry: IProposal): boolean => !refused.some((one: IRefusedProposal): boolean => one.proposal === entry)\n );\n const proposals: IProposalsCargo = {\n tree,\n schema: CARGO_SCHEMA_VERSION,\n items: mine.map((entry: IProposal): IProposalItem => ({ text: entry.body, address: entry.address, resource: entry.resource })),\n };\n const postmortems: IPostmortemsCargo = { tree, schema: CARGO_SCHEMA_VERSION, items: readPostmortems(root, config.postmortems) };\n\n // Проверка на адрес дерева судит все готовые блоки, а не одни уезжающие: отбитый по цитате\n // лежит на диске и уедет, как только его починят, — а найденная в нём утечка отбивает\n // отправку целиком и должна называться сразу.\n const leaked: readonly string[] = leaksOfCargo(cargo, ready, marksOf(root, options.remote));\n if (leaked.length) {\n return refusal(\n `отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`,\n ...leaked,\n 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»'\n );\n }\n\n const going: readonly IShipment[] = shipmentsOf(cargo, proposals, postmortems);\n\n const listed: readonly string[] = manifest(going, cargo, proposals, postmortems);\n // Отбитое называется обоими прогонами: сухой показывает, что уехало бы, — и отбитое к этому\n // относится наравне с уезжающим.\n const refusedLines: readonly string[] = refused.map(\n (one: IRefusedProposal): string => ` отбито ${one.proposal.file}:${one.proposal.line} — ${one.why}`\n );\n\n if (options.dryRun) {\n return {\n code: 0,\n lines: [\n 'СУХОЙ ПРОГОН — наружу не ушло ничего, отметок об отправке не поставлено',\n `уехало бы в ${config.intake}, дерево ${tree}:`,\n ...listed,\n ...refusedLines,\n ...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),\n 'отправляет это тот же вызов без `--dry-run`',\n ],\n };\n }\n\n // Отметка ставится до отправки: отбитое наружу не едет вовсе, и ждать ответа приёма ей\n // незачем.\n markRefused(root, refused);\n\n const outcome: IOutcomeOfCommand = await send(root, config.intake, tree, token, options.ship, going, mine, [\n ...listed,\n ...refusedLines,\n ]);\n\n return outcome;\n}\n"]}
1
+ {"version":3,"file":"shipment.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Сборка груза и команда отправки.\n *\n * Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без\n * замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево\n * переопределило. Отправка, построенная вокруг предложения, эти данные теряла.\n *\n * Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке\n * подменяется двойником.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport {\n CARGO_SCHEMA_VERSION,\n ICargoCount,\n ICargoOverride,\n IPostmortemItem,\n IPostmortemsCargo,\n IProposalItem,\n IProposalsCargo,\n ISummaryCargo,\n} from './cargo.js';\nimport { IEnvironment, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH, IConfig, readConfig } from './config.js';\nimport { IReadResult, ISummary, readObservations, summarize } from './observations.js';\nimport { ILeak, IProposal, leaksIn, markSent, marksOf, nearestMissing, readProposals, TO_PACKAGE } from './proposals.js';\nimport { IShipment, IShipped, readToken, TShip } from './ship.js';\nimport { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';\nimport { byText } from './order.js';\n\n/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */\nconst SLUG_LENGTH: number = 12;\n\n/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */\nconst REFUSED: number = 1;\n\n/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */\nconst TOKEN_REFUSED: number = 401;\n\n/** Чем отправка живёт: чем отправлять, чем себя выдаёт дерево и за какой отрезок сводка. */\nexport interface IShipOptions {\n /** Печатать, что уехало бы, и не отправлять ничего. */\n readonly dryRun: boolean;\n readonly ship: TShip;\n /** Адрес удалённого репозитория этого дерева: из него считается признак. */\n readonly remote: string;\n /** Имена удалённых репозиториев. Нужны отказу: выбор между несколькими делает человек. */\n readonly remotes?: readonly string[];\n /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */\n readonly today: string;\n readonly days: number;\n}\n\n/**\n * Адрес удалённого репозитория, приведённый к одному виду.\n *\n * Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они\n * ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а\n * непривёденные формы дали бы два.\n */\nexport function remoteMarkOf(remote: string): string {\n const named: string = remote\n .trim()\n .replace(/\\.git$/, '')\n .replace(/^[a-z+]+:\\/\\//i, '')\n .replace(/^[^@/]+@/, '')\n .replace(/:/g, '/');\n let end: number = named.length;\n\n while (end > 0 && named[end - 1] === '/') {\n end -= 1;\n }\n\n return named.slice(0, end).toLowerCase();\n}\n\n/**\n * Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.\n *\n * Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих\n * копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе\n * все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.\n */\nexport function treeSlugOf(remote: string, spoken: string): string {\n const mark: string = remoteMarkOf(remote);\n\n return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;\n}\n\n/**\n * Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и\n * наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.\n *\n * Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,\n * а не наблюдения.\n */\nfunction knownCounts(counts: readonly ICargoCount[], known: ReadonlySet<string>): readonly ICargoCount[] {\n return counts.filter((count: ICargoCount): boolean => known.has(count.name));\n}\n\n/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */\nexport function summaryCargo(\n summary: ISummary,\n tree: string,\n overrides: readonly ICargoOverride[],\n unpicked: readonly string[],\n known: ReadonlySet<string>\n): ISummaryCargo {\n return {\n tree,\n unpicked,\n overrides,\n schema: CARGO_SCHEMA_VERSION,\n days: summary.days,\n sessions: summary.sessions,\n loads: knownCounts(summary.loads, known),\n denials: knownCounts(summary.denials, known),\n // Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`\n // или `browser` — слова пакета, одинаковые у всех деревьев.\n kinds: summary.kinds,\n guards: knownCounts(summary.guards, known),\n unused: summary.unused,\n versions: summary.versions,\n total: summary.total,\n };\n}\n\n/**\n * Разборы происшествий, как они лежат на дереве.\n *\n * Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на\n * один вопрос, ради которого его читают.\n */\nexport function readPostmortems(root: string, dir: string): IPostmortemItem[] {\n const path: string = join(root, dir);\n\n if (!existsSync(path)) {\n return [];\n }\n\n return (\n readdirSync(path)\n // Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм\n // промаха, — и в приёме встало бы записью, которой нечего сказать.\n .filter((file: string): boolean => file.endsWith('.md') && file !== 'README.md')\n .sort(byText)\n .map((file: string): IPostmortemItem => ({ file, text: readFileSync(join(path, file), 'utf8') }))\n );\n}\n\n/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */\nexport function shipmentsOf(summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): readonly IShipment[] {\n return [\n { kind: 'сводка', operation: 'summary', body: summary },\n ...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),\n ...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),\n ];\n}\n\n/**\n * Что в грузе называет дерево, из которого он уезжает.\n *\n * Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству\n * называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его\n * отправку.\n */\nexport function leaksOfCargo(summary: ISummaryCargo, proposals: readonly IProposal[], marks: readonly string[]): string[] {\n const inSummary: string[] = leaksIn(JSON.stringify(summary, null, 1), marks).map(\n (leak: ILeak): string => ` сводка: ${leak.why}\\n ${leak.text}`\n );\n const inProposals: string[] = proposals.flatMap((entry: IProposal): string[] =>\n leaksIn(entry.body, marks).map(\n (leak: ILeak): string => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\\n ${leak.text}`\n )\n );\n\n return [...inSummary, ...inProposals];\n}\n\n/** Отказ строками. */\nfunction refusal(...lines: readonly string[]): IOutcomeOfCommand {\n return { code: REFUSED, lines: [...lines] };\n}\n\n/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */\nfunction describe(shipment: IShipment, summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): string {\n if (shipment.operation === 'proposals') {\n return `предложений ${proposals.items.length}`;\n }\n if (shipment.operation === 'postmortems') {\n return `разборов ${postmortems.items.length}`;\n }\n\n return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;\n}\n\n/** Блок, который не уехал, и причина этого. */\ninterface IRefusedProposal {\n readonly proposal: IProposal;\n readonly why: string;\n}\n\n/**\n * Отметка об отбое: отбитый блок остаётся на диске и несёт причину.\n *\n * Удалённый блок пишется следующим заходом заново — следа разбора нет, и повтор возвращается.\n * Отметка держит и то и другое: видно, что разбор был, и видно, почему он не стал правкой.\n */\nfunction markRefused(root: string, refused: readonly IRefusedProposal[]): void {\n for (const one of refused) {\n const path: string = join(root, one.proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), one.proposal, one.why, 'отбито'), 'utf8');\n }\n}\n\n/**\n * Перечень груза: что именно уедет и куда — строками, которые читаются до самой отправки.\n *\n * Печатается он обоими прогонами, и разделены они не окончанием глагола, а первой строкой:\n * «уехало» и «уехало бы» отличаются двумя буквами в хвосте, а строки под ними одинаковы до\n * знака. Прочитанный не тем прогоном, вывод сухого читается сделанной работой — и наоборот,\n * настоящая отправка читается пробой, после которой ничего не делают.\n */\nfunction manifest(\n going: readonly IShipment[],\n summary: ISummaryCargo,\n proposals: IProposalsCargo,\n postmortems: IPostmortemsCargo\n): readonly string[] {\n return going.map((shipment: IShipment): string => ` ${shipment.operation} — ${describe(shipment, summary, proposals, postmortems)}`);\n}\n\n/** Пометка об отправке: по ней предложение второй раз не уезжает. */\nfunction markProposals(root: string, proposals: readonly IProposal[], shipped: IShipped): void {\n const mark: string = `приём:${shipped.accepted?.month ?? 'принято'}`;\n\n for (const proposal of proposals) {\n const path: string = join(root, proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');\n }\n}\n\n/**\n * Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт\n * легшего и уже лежавшего.\n *\n * Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже\n * лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку\n * прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.\n */\nfunction accepted(shipped: IShipped): string {\n if (!shipped.accepted) {\n return 'принято';\n }\n\n const record: string = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;\n const added: number | undefined = shipped.accepted.added;\n\n return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;\n}\n\n/**\n * Отказ, когда признак дерева считать не из чего. Случая два, и говорят они разное: репозитория\n * нет вовсе — считать не из чего; репозиториев несколько и `origin` среди них нет — адрес есть, а\n * выбрать его может только человек. Угаданное неверно сливает в сводке приёма два дерева в одно,\n * и увидеть это нечем.\n */\nfunction noTreeMark(names: readonly string[]): IOutcomeOfCommand {\n if (names.length > 1) {\n return refusal(\n `признак дерева не считается: удалённых репозиториев несколько, а \\`origin\\` среди них нет — ${names.join(', ')}`,\n `переименуй тот, что называет это дерево, в \\`origin\\` — либо назови признак ключом \\`tree\\` в ${CONFIG_PATH}`\n );\n }\n\n return refusal(\n 'признак дерева не считается: удалённого репозитория нет',\n `назови его ключом \\`tree\\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`\n );\n}\n\n/**\n * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе\n * увозит половину и не оставляет следа, по которому видно, какую именно.\n */\nasync function send(\n root: string,\n intake: string,\n tree: string,\n token: string,\n ship: TShip,\n going: readonly IShipment[],\n proposals: readonly IProposal[],\n listed: readonly string[]\n): Promise<IOutcomeOfCommand> {\n // Что уедет, названо до того, как уехало: отказ на втором запросе иначе оставляет человека с\n // одной строкой о нём и без перечня, из которого видно, чего этот отказ стоил.\n const head: readonly string[] = [`ОТПРАВКА — груз уходит в ${intake}, дерево ${tree}:`, ...listed];\n const done: string[] = [];\n\n for (const shipment of going) {\n const shipped: IShipped = await ship(intake, token, shipment);\n\n if (!shipped.ok) {\n return {\n code: REFUSED,\n lines: [\n ...head,\n ...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),\n `${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,\n ...(shipped.status === TOKEN_REFUSED\n ? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']\n : []),\n ],\n };\n }\n\n done.push(` ${shipment.kind} → ${accepted(shipped)}`);\n\n if (shipment.operation === 'proposals') {\n markProposals(root, proposals, shipped);\n }\n }\n\n return { code: 0, lines: [...head, 'уехало:', ...done] };\n}\n\n/**\n * Отправка груза в приём.\n *\n * Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит\n * отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева\n * незачем.\n */\nexport async function propose(env: IEnvironment, options: IShipOptions): Promise<IOutcomeOfCommand> {\n const { root, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n\n if (!config) {\n return refusal(`конфига нет: ${CONFIG_PATH} — заводится \\`agent-kit init\\``);\n }\n if (!config.observe) {\n return {\n code: 0,\n lines: [\n 'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',\n 'выключатель гасит и отправку целиком, вместе со снимком надстроек',\n ],\n };\n }\n if (!config.intake) {\n return refusal(\n 'отправлять некуда: адреса приёма нет',\n `он объявляется ключом \\`intake\\` в ${CONFIG_PATH}, а само значение берётся у владельца приёма — вместе с кодом приглашения`\n );\n }\n\n const tree: string = treeSlugOf(options.remote, config.tree);\n if (!tree) {\n return noTreeMark(options.remotes ?? []);\n }\n\n const token: string = readToken(root, config.token);\n if (!token) {\n return refusal(\n 'токена дерева нет: груз остался неотправленным',\n `файл с ним называется ключом \\`token\\` в ${CONFIG_PATH} и лежит вне дерева`,\n 'выдаётся токен командой приёма — `tree:add` или `tree:token`'\n );\n }\n\n const read: IReadResult = readObservations(root, options.today, options.days);\n const summary: ISummary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);\n const cargo: ISummaryCargo = summaryCargo(\n summary,\n tree,\n treeSnapshot(config, assetsDir, root),\n unpickedOf(config, assetsDir),\n packagedNames(config, assetsDir)\n );\n\n const ready: readonly IProposal[] = readProposals(root).filter(\n (entry: IProposal): boolean => entry.address === TO_PACKAGE && !entry.sent\n );\n // Блок, не назвавший ближайшего утверждения ресурса, не уезжает: разбор, кончившийся ещё\n // одной статьёй о том, о чём статья уже стоит, снаружи неотличим от разбора, кончившегося\n // исправлением. Отбивается он поимённо, а остальные едут: один непрочитанный ресурс не\n // повод задержать чужую работу.\n const refused: readonly IRefusedProposal[] = ready\n .map((entry: IProposal): IRefusedProposal => ({ proposal: entry, why: nearestMissing(entry, assetsDir) }))\n .filter((entry: IRefusedProposal): boolean => Boolean(entry.why));\n const mine: readonly IProposal[] = ready.filter(\n (entry: IProposal): boolean => !refused.some((one: IRefusedProposal): boolean => one.proposal === entry)\n );\n const proposals: IProposalsCargo = {\n tree,\n schema: CARGO_SCHEMA_VERSION,\n items: mine.map((entry: IProposal): IProposalItem => ({ text: entry.body, address: entry.address, resource: entry.resource })),\n };\n const postmortems: IPostmortemsCargo = { tree, schema: CARGO_SCHEMA_VERSION, items: readPostmortems(root, config.postmortems) };\n\n // Проверка на адрес дерева судит все готовые блоки, а не одни уезжающие: отбитый по цитате\n // лежит на диске и уедет, как только его починят, — а найденная в нём утечка отбивает\n // отправку целиком и должна называться сразу.\n const leaked: readonly string[] = leaksOfCargo(cargo, ready, marksOf(root, options.remote));\n if (leaked.length) {\n return refusal(\n `отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`,\n ...leaked,\n 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»'\n );\n }\n\n const going: readonly IShipment[] = shipmentsOf(cargo, proposals, postmortems);\n\n const listed: readonly string[] = manifest(going, cargo, proposals, postmortems);\n // Отбитое называется обоими прогонами: сухой показывает, что уехало бы, — и отбитое к этому\n // относится наравне с уезжающим.\n const refusedLines: readonly string[] = refused.map(\n (one: IRefusedProposal): string => ` отбито ${one.proposal.file}:${one.proposal.line} — ${one.why}`\n );\n\n if (options.dryRun) {\n return {\n code: 0,\n lines: [\n 'СУХОЙ ПРОГОН — наружу не ушло ничего, отметок об отправке не поставлено',\n `уехало бы в ${config.intake}, дерево ${tree}:`,\n ...listed,\n ...refusedLines,\n ...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),\n 'отправляет это тот же вызов без `--dry-run`',\n ],\n };\n }\n\n // Отметка ставится до отправки: отбитое наружу не едет вовсе, и ждать ответа приёма ей\n // незачем.\n markRefused(root, refused);\n\n const outcome: IOutcomeOfCommand = await send(root, config.intake, tree, token, options.ship, going, mine, [\n ...listed,\n ...refusedLines,\n ]);\n\n return outcome;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rt-tools/agent-kit",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Переносимый слой правил для агента: законы, хуки, проверки и агенты, раскладываемые в репозиторий одной командой",
5
5
  "author": "RT Team",
6
6
  "license": "Apache-2.0",