@rt-tools/agent-kit 0.11.0 → 0.13.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 (138) hide show
  1. package/assets/checks/board-runs.github.mjs +87 -0
  2. package/assets/checks/board.github.mjs +0 -40
  3. package/assets/checks/check-board.github.mjs +39 -8
  4. package/assets/checks/check-file-size.mjs +19 -4
  5. package/assets/checks/check-schema-drift.mjs +28 -5
  6. package/assets/checks/check-state-next.mjs +10 -2
  7. package/assets/checks/rt-kit-checks.config.mjs +28 -2
  8. package/assets/commands/feedback.md +8 -0
  9. package/assets/defaults/project.sh +59 -6
  10. package/assets/defaults/turn-map.md +8 -6
  11. package/assets/hooks/browser-guard-device-id.sh +3 -1
  12. package/assets/hooks/browser-guard-no-asking.sh +3 -1
  13. package/assets/hooks/browser-guard-no-other-drivers.sh +6 -4
  14. package/assets/hooks/browser-guard-require-select.sh +4 -2
  15. package/assets/hooks/claim-guard.sh +3 -1
  16. package/assets/hooks/conscience-guard.sh +3 -1
  17. package/assets/hooks/dev-server-guard.sh +6 -4
  18. package/assets/hooks/dispatch.sh +69 -0
  19. package/assets/hooks/docs-guard.sh +6 -4
  20. package/assets/hooks/exam-guard.sh +5 -3
  21. package/assets/hooks/git-guard-delivery-folder.sh +99 -0
  22. package/assets/hooks/git-guard-delivery-signature.sh +10 -4
  23. package/assets/hooks/git-guard-delivery.sh +58 -74
  24. package/assets/hooks/git-guard-main.sh +6 -4
  25. package/assets/hooks/git-guard-push-tests.sh +8 -6
  26. package/assets/hooks/grill-gate.sh +4 -2
  27. package/assets/hooks/handoff-entry-guard.sh +4 -2
  28. package/assets/hooks/handoff-write.sh +27 -6
  29. package/assets/hooks/hook-input.sh +77 -0
  30. package/assets/hooks/lint-after-edit.sh +5 -3
  31. package/assets/hooks/override-write-guard.sh +107 -0
  32. package/assets/hooks/postmortem-guard.sh +3 -1
  33. package/assets/hooks/proposal-guard.sh +3 -1
  34. package/assets/hooks/prose-style-guard.sh +5 -3
  35. package/assets/hooks/qa-dataid-guard.sh +4 -2
  36. package/assets/hooks/rerun-guard.sh +6 -4
  37. package/assets/hooks/reuse-first-guard.sh +5 -3
  38. package/assets/hooks/rule-article.sh +99 -0
  39. package/assets/hooks/rule-source-guard.sh +126 -0
  40. package/assets/hooks/skill-gate-rearm.sh +3 -1
  41. package/assets/hooks/skill-gate.sh +23 -2
  42. package/assets/hooks/skill-loaded.sh +3 -1
  43. package/assets/hooks/sql-guard-request.sh +2 -1
  44. package/assets/hooks/sql-guard.sh +4 -2
  45. package/assets/hooks/task-flow-guard.sh +24 -4
  46. package/assets/hooks/turn-exit-guard.sh +59 -17
  47. package/assets/hooks/waiting-turn-guard.sh +3 -1
  48. package/assets/hooks/window-fill-guard.sh +6 -4
  49. package/assets/laws/work-conduct.md +5 -9
  50. package/assets/patterns/dependencies-upgrade.md +1 -1
  51. package/assets/patterns/doc-style-write.md +3 -3
  52. package/assets/patterns/git-workflow-commit.azure.md +2 -202
  53. package/assets/patterns/git-workflow-commit.github.md +2 -258
  54. package/assets/patterns/git-workflow-commit.gitlab.md +1 -217
  55. package/assets/patterns/git-workflow-docker.md +3 -3
  56. package/assets/patterns/git-workflow-merge.md +3 -2
  57. package/assets/patterns/git-workflow-migration.md +3 -3
  58. package/assets/patterns/git-workflow-pr.azure.md +224 -0
  59. package/assets/patterns/git-workflow-pr.github.md +280 -0
  60. package/assets/patterns/git-workflow-pr.gitlab.md +240 -0
  61. package/assets/patterns/git-workflow-restart.md +3 -3
  62. package/assets/patterns/git-workflow-secrets.md +3 -3
  63. package/assets/patterns/task-flow-archive.md +195 -0
  64. package/assets/patterns/task-flow-close.md +72 -240
  65. package/assets/patterns/task-flow-handoff.md +4 -4
  66. package/assets/patterns/task-flow-resume.md +5 -3
  67. package/assets/pitfalls/agent-kit.md +80 -0
  68. package/assets/pitfalls/doc-style.md +80 -0
  69. package/assets/pitfalls/git-workflow.azure.md +50 -0
  70. package/assets/pitfalls/git-workflow.github.md +78 -0
  71. package/assets/pitfalls/git-workflow.gitlab.md +49 -0
  72. package/assets/pitfalls/spec-driven.md +36 -0
  73. package/assets/pitfalls/styling-bem.md +45 -0
  74. package/assets/pitfalls/task-flow.md +62 -0
  75. package/assets/pitfalls/testing.md +70 -0
  76. package/assets/rules/deploy-flow.azure.md +114 -0
  77. package/assets/rules/deploy-flow.github.md +122 -0
  78. package/assets/rules/deploy-flow.gitlab.md +116 -0
  79. package/assets/rules/doc-style.md +25 -76
  80. package/assets/rules/git-workflow.azure.md +6 -92
  81. package/assets/rules/git-workflow.github.md +20 -128
  82. package/assets/rules/git-workflow.gitlab.md +6 -93
  83. package/assets/rules/spec-driven.md +39 -30
  84. package/assets/rules/styling-bem.md +20 -39
  85. package/assets/rules/task-flow.md +56 -222
  86. package/assets/rules/testing.md +3 -64
  87. package/assets/rules/turn-conduct.md +210 -0
  88. package/assets/rules/typescript-conventions.md +15 -0
  89. package/assets/skills/agent-kit.md +67 -89
  90. package/assets/templates/pitfalls.md +10 -0
  91. package/assets/templates/proposal.md +16 -1
  92. package/assets/templates/rule.md +5 -3
  93. package/bin/agent-kit.d.ts.map +1 -1
  94. package/bin/agent-kit.js +1 -42
  95. package/bin/agent-kit.js.map +1 -1
  96. package/lib/assets.d.ts.map +1 -1
  97. package/lib/assets.js +6 -1
  98. package/lib/assets.js.map +1 -1
  99. package/lib/cascade.d.ts.map +1 -1
  100. package/lib/cascade.js +19 -1
  101. package/lib/cascade.js.map +1 -1
  102. package/lib/commands.d.ts.map +1 -1
  103. package/lib/commands.js +1 -0
  104. package/lib/commands.js.map +1 -1
  105. package/lib/config.d.ts +16 -1
  106. package/lib/config.d.ts.map +1 -1
  107. package/lib/config.js +8 -0
  108. package/lib/config.js.map +1 -1
  109. package/lib/hooks-map.d.ts +13 -0
  110. package/lib/hooks-map.d.ts.map +1 -1
  111. package/lib/hooks-map.js +33 -1
  112. package/lib/hooks-map.js.map +1 -1
  113. package/lib/proposals.d.ts +5 -1
  114. package/lib/proposals.d.ts.map +1 -1
  115. package/lib/proposals.js +74 -5
  116. package/lib/proposals.js.map +1 -1
  117. package/lib/ship.d.ts +1 -2
  118. package/lib/ship.d.ts.map +1 -1
  119. package/lib/ship.js +0 -54
  120. package/lib/ship.js.map +1 -1
  121. package/lib/shipment.d.ts.map +1 -1
  122. package/lib/shipment.fixture.d.ts +39 -0
  123. package/lib/shipment.fixture.d.ts.map +1 -0
  124. package/lib/shipment.fixture.js +99 -0
  125. package/lib/shipment.fixture.js.map +1 -0
  126. package/lib/shipment.js +59 -7
  127. package/lib/shipment.js.map +1 -1
  128. package/package.json +1 -1
  129. package/rt-tools-agent-kit-0.13.0.tgz +0 -0
  130. package/assets/commands/agent-kit-digest.md +0 -89
  131. package/assets/commands/rules-review.md +0 -98
  132. package/assets/patterns/cargo-triage-mark.md +0 -119
  133. package/assets/rules/cargo-triage.md +0 -126
  134. package/lib/cargo-state.d.ts +0 -62
  135. package/lib/cargo-state.d.ts.map +0 -1
  136. package/lib/cargo-state.js +0 -118
  137. package/lib/cargo-state.js.map +0 -1
  138. package/rt-tools-agent-kit-0.11.0.tgz +0 -0
package/lib/config.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind =\n | 'laws'\n | 'rules'\n | 'patterns'\n | 'skills'\n | 'hooks'\n | 'defaults'\n | 'checks'\n | 'agents'\n | 'commands'\n | 'workflows'\n | 'templates'\n /** Документы дерева, которые читает и человек, и агент: словарь проекта. */\n | 'docs'\n /**\n * Образцы, которые копируют в рабочий файл и заполняют: папка задачи, спек домена.\n *\n * От шаблонов отличаются читателем и местом. Шаблон ложится в каталог слоя правил, и с него\n * пишут ресурс этого слоя; образец ложится туда, где идёт работа, и копирует его тот, кто\n * работу ведёт. Одним родом их не свести: дерево вправе отказаться от одних, оставив\n * другие, и назвать им разные каталоги.\n */\n | 'samples';\n\nexport const KINDS: readonly TKind[] = [\n 'laws',\n 'rules',\n 'patterns',\n 'skills',\n 'hooks',\n 'defaults',\n 'checks',\n 'agents',\n 'commands',\n 'workflows',\n 'templates',\n 'docs',\n 'samples',\n];\n\n/**\n * Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как\n * `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.\n *\n * `skills` — скил, у которого нет закона над собой: он не про то, что должно быть верно в\n * продукте, а про то, как здесь делается работа. Правилом его называть нельзя — тогда придётся\n * выдумать ему закон, — а паттерном тем более: паттерн стоит при правиле.\n */\nexport const SKILL_KINDS: readonly TKind[] = ['rules', 'patterns', 'skills'];\n\n/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */\nexport const SKILL_FILE: string = 'SKILL.md';\n\n/**\n * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.\n *\n * Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один\n * раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.\n */\nexport const COMPANION_FILE: string = 'implementation.md';\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /**\n * Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.\n * Ограничивает только те роды, которые сам называет; как именно — `isChosen`.\n */\n readonly only: readonly string[];\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/application/money.md`. */\n readonly skip: readonly string[];\n /**\n * Выбор проекта по осям различия: `{ \"host\": \"gitlab\" }`. Оси объявлены пакетом в\n * `assets/variants.json`, а ресурс с вариантом в имени берётся, только если проект выбрал\n * его значение; как именно — `isChosen`.\n */\n readonly variants: Readonly<Record<string, string>>;\n /**\n * Что у дерева есть: `[\"packages\", \"app\"]`. Свойства объявлены пакетом в `assets/traits.json`,\n * а ресурс, требующий свойства именем файла, дереву без него не кладётся вовсе — ни сам, ни\n * черновик его компаньона. Пусто — дерево о себе ничего не сказало, и помеченного не получает.\n */\n readonly has: readonly string[];\n /**\n * Пишут ли гарды наблюдения о слое правил. Умолчание — да; выключает дерево, и целиком, а\n * не частями. Ключ читают двое: сводка — отсюда, гард — тем же именем прямо из файла, потому\n * что разборщика конфига у него нет.\n */\n readonly observe: boolean;\n /**\n * Роли слоя правил, которые дерево выключило, именами файлов без расширения:\n * `[\"strict-teacher\"]`. Выключается обязательность вызова, а не сама роль: файл роли\n * остаётся разложенным и зовётся руками, а гард при выключенной роли выходит молча.\n * Ключ читают двое: сводка — отсюда, гард — тем же именем прямо из файла, потому что\n * разборщика конфига у него нет.\n */\n readonly rolesOff: readonly string[];\n /**\n * Адрес приёма, куда уезжает груз: `https://…`. Пусто — отправлять некуда, и отправка\n * отказывает вместо молчания. Зашитый в код адрес назвал бы чужое дерево в текстах пакета.\n */\n readonly intake: string;\n /**\n * Файл с токеном дерева — путь от корня дерева или от домашнего каталога (`~/…`). Сам токен\n * в конфиге не лежит: конфиг коммитится, а токен обязан остаться вне истории.\n */\n readonly token: string;\n /**\n * Признак дерева, когда удалённого репозитория нет. При нём — считается из адреса\n * репозитория: две рабочие копии одного репозитория обязаны дать один признак.\n */\n readonly tree: string;\n /** Где дерево держит разборы происшествий. Путь от корня дерева. */\n readonly postmortems: string;\n}\n\nexport const CONFIG_PATH: string = '.claude/rt-kit.json';\n/** Каталог, в котором дерево держит своё при пакете: надстройки, умолчания, свою карту и профиль. */\nexport const RT_KIT_DIR: string = '.claude/rt-kit';\nexport const OVERRIDES_DIR: string = '.claude/rt-kit/overrides';\n\n/**\n * Где разборы происшествий лежат, когда конфиг не сказал иначе. Роды ресурсов сюда не годятся:\n * разборы пишет дерево про себя, пакет их не раскладывает и не сверяет.\n */\nexport const DEFAULT_POSTMORTEMS_DIR: string = 'docs/postmortems';\n\n/**\n * Карта «что правится — какое правило» и профиль дерева: команды, стенды, пары «правка —\n * документ». Оба лежат в двух видах, и это не дублирование, а разделение ответственности.\n *\n * Умолчание везёт пакет: проекты этой мастерской устроены одинаково — Nx, те же расширения, те\n * же имена каталогов, — и переписывать одну и ту же карту в каждом дереве заново значило бы\n * заводить пятнадцать её редакций, расходящихся молча. Оно разложено, под шапкой, и правится\n * не на месте.\n *\n * Надстройку пишет проект, и она необязательна: витрина, свой род файлов, другой запускатель\n * тестов есть не у всех. Объявленная в ней функция замещает умолчание целиком и вправе позвать\n * его обратно суффиксом `_default` — так дерево дописывает своё, не теряя общего.\n */\n/** Каталог правил дерева: законы туда не идут, а правила, паттерны и скилы лежат вместе. */\nexport const SKILLS_DIR: string = '.claude/skills';\n\nexport const DEFAULTS_DIR: string = '.claude/rt-kit/defaults';\nexport const GATE_MAP_FILE: string = 'gate-map.sh';\nexport const PROFILE_FILE: string = 'project.sh';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n rules: SKILLS_DIR,\n patterns: SKILLS_DIR,\n skills: SKILLS_DIR,\n hooks: '.claude/hooks',\n defaults: DEFAULTS_DIR,\n checks: 'tools',\n agents: '.claude/agents',\n commands: '.claude/commands',\n workflows: '.claude/workflows',\n templates: '.claude/rt-kit/templates',\n docs: 'docs',\n // Внутри рода путь повторяет раскладку дерева — папки задач и спеки, — поэтому одного\n // умолчания хватает всем образцам сразу.\n samples: 'docs',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\n/** Строковый ключ конфига. Ключа нет — пустая строка: значение необязательное у всех четырёх. */\nfunction textOf(value: unknown, where: string): string {\n if (value === undefined || value === null) {\n return '';\n }\n if (typeof value !== 'string') {\n throw new ConfigError(`${where}: ожидается строка`);\n }\n\n return value.trim();\n}\n\nfunction idList(value: unknown, where: string): readonly string[] {\n const list: unknown = value ?? [];\n if (!Array.isArray(list) || list.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);\n }\n\n return list as string[];\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const only: readonly string[] = idList(raw['only'], 'only');\n const skip: readonly string[] = idList(raw['skip'], 'skip');\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n\n return {\n only,\n skip,\n vars: { ...derived, ...stringMap(raw['vars'], 'vars') },\n layout: layout as Record<TKind, string>,\n variants: stringMap(raw['variants'], 'variants'),\n has: idList(raw['has'], 'has'),\n // Выключателем считается только явное «нет»: ключа нет — запись идёт, как и у гарда.\n observe: raw['observe'] !== false,\n rolesOff: idList(raw['rolesOff'], 'rolesOff'),\n intake: textOf(raw['intake'], 'intake'),\n token: textOf(raw['token'], 'token'),\n tree: textOf(raw['tree'], 'tree'),\n postmortems: textOf(raw['postmortems'], 'postmortems') || DEFAULT_POSTMORTEMS_DIR,\n };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAqCjC,MAAM,CAAC,MAAM,KAAK,GAAqB;IACnC,MAAM;IACN,OAAO;IACP,UAAU;IACV,UAAU;IACV,QAAQ;IACR,OAAO;IACP,UAAU;IACV,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,WAAW;IACX,WAAW;IACX,MAAM;IACN,SAAS;CACZ,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAE7E,iFAAiF;AACjF,MAAM,CAAC,MAAM,UAAU,GAAW,UAAU,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAW,mBAAmB,CAAC;AAE1D;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAW,aAAa,CAAC;AA2DnD,MAAM,CAAC,MAAM,WAAW,GAAW,qBAAqB,CAAC;AACzD,qGAAqG;AACrG,MAAM,CAAC,MAAM,UAAU,GAAW,gBAAgB,CAAC;AACnD,MAAM,CAAC,MAAM,aAAa,GAAW,0BAA0B,CAAC;AAEhE;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAW,kBAAkB,CAAC;AAElE;;;;;;;;;;;;GAYG;AACH,4FAA4F;AAC5F,MAAM,CAAC,MAAM,UAAU,GAAW,gBAAgB,CAAC;AAEnD,MAAM,CAAC,MAAM,YAAY,GAAW,yBAAyB,CAAC;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAW,aAAa,CAAC;AACnD,MAAM,CAAC,MAAM,YAAY,GAAW,YAAY,CAAC;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAoC;IAC3D,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,UAAU;IACjB,8EAA8E;IAC9E,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,eAAe;IACtB,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,OAAO;IACf,MAAM,EAAE,gBAAgB;IACxB,QAAQ,EAAE,kBAAkB;IAC5B,SAAS,EAAE,mBAAmB;IAC9B,SAAS,EAAE,0BAA0B;IACrC,IAAI,EAAE,MAAM;IACZ,sFAAsF;IACtF,yCAAyC;IACzC,OAAO,EAAE,MAAM;CAClB,CAAC;AAEF,MAAM,OAAO,WAAY,SAAQ,KAAK;CAAG;AAEzC,MAAM,QAAQ,GAAyD,CAAC,KAAc,EAAoC,EAAE,CACxH,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEzE,SAAS,SAAS,CAAC,KAAc,EAAE,KAAa;IAC5C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACd,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,qCAAqC,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,IAAI,GAAG,oBAAoB,CAAC,CAAC;QAC/D,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACxB,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,iGAAiG;AACjG,SAAS,MAAM,CAAC,KAAc,EAAE,KAAa;IACzC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,EAAE,CAAC;IACd,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,MAAM,CAAC,KAAc,EAAE,KAAa;IACzC,MAAM,IAAI,GAAY,KAAK,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,KAAc,EAAW,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,EAAE,CAAC;QAC5F,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,6CAA6C,CAAC,CAAC;IACjF,CAAC;IAED,OAAO,IAAgB,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACpC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACD,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACtB,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,+BAAgC,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,oBAAoB,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,MAAM,GAA2B,EAAE,GAAG,cAAc,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;IACpG,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAa,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,WAAW,CAAC,UAAU,IAAI,qCAAqC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjG,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAsB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAsB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAE5D,4FAA4F;IAC5F,8EAA8E;IAC9E,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO;QACH,IAAI;QACJ,IAAI;QACJ,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE;QACvD,MAAM,EAAE,MAA+B;QACvC,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;QAChD,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;QAC9B,qFAAqF;QACrF,OAAO,EAAE,GAAG,CAAC,SAAS,CAAC,KAAK,KAAK;QACjC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;QAC7C,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;QACvC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;QACpC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QACjC,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC,IAAI,uBAAuB;KACpF,CAAC;AACN,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,UAAU,CAAC,IAAY;IACnC,IAAI,CAAC;QACD,OAAO,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACtB,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YAC/B,MAAM,KAAK,CAAC;QAChB,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind =\n | 'laws'\n | 'rules'\n | 'patterns'\n | 'skills'\n | 'hooks'\n | 'defaults'\n | 'checks'\n | 'agents'\n | 'commands'\n | 'workflows'\n | 'templates'\n /** Документы дерева, которые читает и человек, и агент: словарь проекта. */\n | 'docs'\n /**\n * Образцы, которые копируют в рабочий файл и заполняют: папка задачи, спек домена.\n *\n * От шаблонов отличаются читателем и местом. Шаблон ложится в каталог слоя правил, и с него\n * пишут ресурс этого слоя; образец ложится туда, где идёт работа, и копирует его тот, кто\n * работу ведёт. Одним родом их не свести: дерево вправе отказаться от одних, оставив\n * другие, и назвать им разные каталоги.\n */\n | 'samples'\n /**\n * `pitfalls` — холодная часть правила: ловушки и поведение по разборам происшествий. Ложится\n * третьим файлом рядом с правилом и грузится не вместе с ним, а по требованию: при обычном\n * решении она не нужна — она нужна тому, кто разбирает промах или спорит с гардом.\n *\n * От спутника отличается хозяином. Спутник пишет проект, и пакет кладёт его черновиком один\n * раз; холодная часть принадлежит пакету целиком, поэтому перекладывается на каждой\n * раскладке и сверяется наравне с правилом.\n */\n | 'pitfalls';\n\nexport const KINDS: readonly TKind[] = [\n 'laws',\n 'rules',\n 'pitfalls',\n 'patterns',\n 'skills',\n 'hooks',\n 'defaults',\n 'checks',\n 'agents',\n 'commands',\n 'workflows',\n 'templates',\n 'docs',\n 'samples',\n];\n\n/**\n * Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как\n * `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.\n *\n * `skills` — скил, у которого нет закона над собой: он не про то, что должно быть верно в\n * продукте, а про то, как здесь делается работа. Правилом его называть нельзя — тогда придётся\n * выдумать ему закон, — а паттерном тем более: паттерн стоит при правиле.\n */\nexport const SKILL_KINDS: readonly TKind[] = ['rules', 'patterns', 'skills'];\n\n/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */\nexport const SKILL_FILE: string = 'SKILL.md';\n\n/**\n * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.\n *\n * Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один\n * раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.\n */\nexport const COMPANION_FILE: string = 'implementation.md';\n\n/**\n * Файл холодной части правила. Имя одно на все деревья: правило называет её строкой, и строка\n * эта переносима ровно потому, что имя не выбирается на месте.\n */\nexport const PITFALLS_FILE: string = 'pitfalls.md';\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /**\n * Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.\n * Ограничивает только те роды, которые сам называет; как именно — `isChosen`.\n */\n readonly only: readonly string[];\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/application/money.md`. */\n readonly skip: readonly string[];\n /**\n * Выбор проекта по осям различия: `{ \"host\": \"gitlab\" }`. Оси объявлены пакетом в\n * `assets/variants.json`, а ресурс с вариантом в имени берётся, только если проект выбрал\n * его значение; как именно — `isChosen`.\n */\n readonly variants: Readonly<Record<string, string>>;\n /**\n * Что у дерева есть: `[\"packages\", \"app\"]`. Свойства объявлены пакетом в `assets/traits.json`,\n * а ресурс, требующий свойства именем файла, дереву без него не кладётся вовсе — ни сам, ни\n * черновик его компаньона. Пусто — дерево о себе ничего не сказало, и помеченного не получает.\n */\n readonly has: readonly string[];\n /**\n * Пишут ли гарды наблюдения о слое правил. Умолчание — да; выключает дерево, и целиком, а\n * не частями. Ключ читают двое: сводка — отсюда, гард — тем же именем прямо из файла, потому\n * что разборщика конфига у него нет.\n */\n readonly observe: boolean;\n /**\n * Роли слоя правил, которые дерево выключило, именами файлов без расширения:\n * `[\"strict-teacher\"]`. Выключается обязательность вызова, а не сама роль: файл роли\n * остаётся разложенным и зовётся руками, а гард при выключенной роли выходит молча.\n * Ключ читают двое: сводка — отсюда, гард — тем же именем прямо из файла, потому что\n * разборщика конфига у него нет.\n */\n readonly rolesOff: readonly string[];\n /**\n * Адрес приёма, куда уезжает груз: `https://…`. Пусто — отправлять некуда, и отправка\n * отказывает вместо молчания. Зашитый в код адрес назвал бы чужое дерево в текстах пакета.\n */\n readonly intake: string;\n /**\n * Файл с токеном дерева — путь от корня дерева или от домашнего каталога (`~/…`). Сам токен\n * в конфиге не лежит: конфиг коммитится, а токен обязан остаться вне истории.\n */\n readonly token: string;\n /**\n * Признак дерева, когда удалённого репозитория нет. При нём — считается из адреса\n * репозитория: две рабочие копии одного репозитория обязаны дать один признак.\n */\n readonly tree: string;\n /** Где дерево держит разборы происшествий. Путь от корня дерева. */\n readonly postmortems: string;\n}\n\nexport const CONFIG_PATH: string = '.claude/rt-kit.json';\n/** Каталог, в котором дерево держит своё при пакете: надстройки, умолчания, свою карту и профиль. */\nexport const RT_KIT_DIR: string = '.claude/rt-kit';\nexport const OVERRIDES_DIR: string = '.claude/rt-kit/overrides';\n\n/**\n * Где разборы происшествий лежат, когда конфиг не сказал иначе. Роды ресурсов сюда не годятся:\n * разборы пишет дерево про себя, пакет их не раскладывает и не сверяет.\n */\nexport const DEFAULT_POSTMORTEMS_DIR: string = 'docs/postmortems';\n\n/**\n * Карта «что правится — какое правило» и профиль дерева: команды, стенды, пары «правка —\n * документ». Оба лежат в двух видах, и это не дублирование, а разделение ответственности.\n *\n * Умолчание везёт пакет: проекты этой мастерской устроены одинаково — Nx, те же расширения, те\n * же имена каталогов, — и переписывать одну и ту же карту в каждом дереве заново значило бы\n * заводить пятнадцать её редакций, расходящихся молча. Оно разложено, под шапкой, и правится\n * не на месте.\n *\n * Надстройку пишет проект, и она необязательна: витрина, свой род файлов, другой запускатель\n * тестов есть не у всех. Объявленная в ней функция замещает умолчание целиком и вправе позвать\n * его обратно суффиксом `_default` — так дерево дописывает своё, не теряя общего.\n */\n/** Каталог правил дерева: законы туда не идут, а правила, паттерны и скилы лежат вместе. */\nexport const SKILLS_DIR: string = '.claude/skills';\n\nexport const DEFAULTS_DIR: string = '.claude/rt-kit/defaults';\nexport const GATE_MAP_FILE: string = 'gate-map.sh';\nexport const PROFILE_FILE: string = 'project.sh';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n rules: SKILLS_DIR,\n // Холодная часть ложится в каталог своего правила, поэтому путь у неё тот же.\n pitfalls: SKILLS_DIR,\n patterns: SKILLS_DIR,\n skills: SKILLS_DIR,\n hooks: '.claude/hooks',\n defaults: DEFAULTS_DIR,\n checks: 'tools',\n agents: '.claude/agents',\n commands: '.claude/commands',\n workflows: '.claude/workflows',\n templates: '.claude/rt-kit/templates',\n docs: 'docs',\n // Внутри рода путь повторяет раскладку дерева — папки задач и спеки, — поэтому одного\n // умолчания хватает всем образцам сразу.\n samples: 'docs',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\n/** Строковый ключ конфига. Ключа нет — пустая строка: значение необязательное у всех четырёх. */\nfunction textOf(value: unknown, where: string): string {\n if (value === undefined || value === null) {\n return '';\n }\n if (typeof value !== 'string') {\n throw new ConfigError(`${where}: ожидается строка`);\n }\n\n return value.trim();\n}\n\nfunction idList(value: unknown, where: string): readonly string[] {\n const list: unknown = value ?? [];\n if (!Array.isArray(list) || list.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);\n }\n\n return list as string[];\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const only: readonly string[] = idList(raw['only'], 'only');\n const skip: readonly string[] = idList(raw['skip'], 'skip');\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n\n return {\n only,\n skip,\n vars: { ...derived, ...stringMap(raw['vars'], 'vars') },\n layout: layout as Record<TKind, string>,\n variants: stringMap(raw['variants'], 'variants'),\n has: idList(raw['has'], 'has'),\n // Выключателем считается только явное «нет»: ключа нет — запись идёт, как и у гарда.\n observe: raw['observe'] !== false,\n rolesOff: idList(raw['rolesOff'], 'rolesOff'),\n intake: textOf(raw['intake'], 'intake'),\n token: textOf(raw['token'], 'token'),\n tree: textOf(raw['tree'], 'tree'),\n postmortems: textOf(raw['postmortems'], 'postmortems') || DEFAULT_POSTMORTEMS_DIR,\n };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
@@ -25,7 +25,20 @@ export declare function bindingsOf(text: string, path: string): readonly IHookBi
25
25
  * Гарды с одинаковым образцом внутри события собираются в одну запись — так же, как это пишут
26
26
  * руками, и так же, как читает агент.
27
27
  */
28
+ /** Диспетчер событий: одна запись в настройке вместо списка гардов. Путь от корня дерева. */
29
+ export declare const DISPATCH_PATH: string;
30
+ /**
31
+ * Кусок настройки агента: по строке на событие, и каждая зовёт диспетчер с именем события.
32
+ *
33
+ * Прежде здесь стоял список гардов — по записи на образец вызова, — и агент запускал каждый
34
+ * своим процессом, подавая всем один и тот же ввод. Восемнадцать гардов на вызове инструмента
35
+ * разбирали его восемнадцать раз; замер дерева, где это считали, дал восемьсот двадцать шесть
36
+ * миллисекунд на вызов против четырёхсот сорока одной после. Образец вызова никуда не делся:
37
+ * его по-прежнему несёт шапка самого гарда, только сверяет его теперь диспетчер, а не агент.
38
+ */
28
39
  export declare function hooksSection(bindings: readonly IHookBinding[]): Record<string, unknown>;
40
+ /** Прежний вид куска настройки: по записи на образец вызова. Остаётся ради разбора чужих настроек. */
41
+ export declare function hooksSectionByMatcher(bindings: readonly IHookBinding[]): Record<string, unknown>;
29
42
  /** Пути гардов, названные в настройке агента этого дерева. Нет файла — ни одного. */
30
43
  export declare function boundInSettings(root: string): readonly string[];
31
44
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"hooks-map.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/hooks-map.ts"],"names":[],"mappings":"AAoBA,qEAAqE;AACrE,eAAO,MAAM,aAAa,EAAE,MAAgC,CAAC;AAI7D,MAAM,WAAW,YAAY;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,YAAY,EAAE,CAM9E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwBvF;AAiDD,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAsB/D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,YAAY,EAAE,CAIrG;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAa5E;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAczG"}
1
+ {"version":3,"file":"hooks-map.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/hooks-map.ts"],"names":[],"mappings":"AAoBA,qEAAqE;AACrE,eAAO,MAAM,aAAa,EAAE,MAAgC,CAAC;AAI7D,MAAM,WAAW,YAAY;IACzB,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,YAAY,EAAE,CAM9E;AAED;;;;;GAKG;AACH,6FAA6F;AAC7F,eAAO,MAAM,aAAa,EAAE,MAAoC,CAAC;AAUjE;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASvF;AAED,sGAAsG;AACtG,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwBhG;AAiDD,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAsB/D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,YAAY,EAAE,CAYrG;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAa5E;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAczG"}
package/lib/hooks-map.js CHANGED
@@ -40,7 +40,34 @@ export function bindingsOf(text, path) {
40
40
  * Гарды с одинаковым образцом внутри события собираются в одну запись — так же, как это пишут
41
41
  * руками, и так же, как читает агент.
42
42
  */
43
+ /** Диспетчер событий: одна запись в настройке вместо списка гардов. Путь от корня дерева. */
44
+ export const DISPATCH_PATH = '.claude/hooks/dispatch.sh';
45
+ /**
46
+ * Стоит ли в настройке диспетчер этого события. Он зовётся с именем события доводом, поэтому
47
+ * найденный путь без довода — не он: так стояла бы запись, зовущая диспетчер на всём подряд.
48
+ */
49
+ function dispatchesEvent(text, event) {
50
+ return text.includes(`${DISPATCH_PATH} ${event}`);
51
+ }
52
+ /**
53
+ * Кусок настройки агента: по строке на событие, и каждая зовёт диспетчер с именем события.
54
+ *
55
+ * Прежде здесь стоял список гардов — по записи на образец вызова, — и агент запускал каждый
56
+ * своим процессом, подавая всем один и тот же ввод. Восемнадцать гардов на вызове инструмента
57
+ * разбирали его восемнадцать раз; замер дерева, где это считали, дал восемьсот двадцать шесть
58
+ * миллисекунд на вызов против четырёхсот сорока одной после. Образец вызова никуда не делся:
59
+ * его по-прежнему несёт шапка самого гарда, только сверяет его теперь диспетчер, а не агент.
60
+ */
43
61
  export function hooksSection(bindings) {
62
+ const events = new Set(bindings.map((binding) => binding.event));
63
+ const section = {};
64
+ for (const event of [...events].sort((left, right) => left.localeCompare(right))) {
65
+ section[event] = [{ hooks: [{ type: 'command', command: `$CLAUDE_PROJECT_DIR/${DISPATCH_PATH} ${event}` }] }];
66
+ }
67
+ return section;
68
+ }
69
+ /** Прежний вид куска настройки: по записи на образец вызова. Остаётся ради разбора чужих настроек. */
70
+ export function hooksSectionByMatcher(bindings) {
44
71
  const events = new Map();
45
72
  for (const binding of bindings) {
46
73
  const byMatcher = events.get(binding.event) ?? new Map();
@@ -126,7 +153,12 @@ export function boundInSettings(root) {
126
153
  */
127
154
  export function unboundHooks(bindings, root) {
128
155
  const bound = new Set(boundInSettings(root));
129
- return bindings.filter((binding) => !bound.has(`${binding.event} ${binding.path}`) && !bound.has(binding.path));
156
+ const path = join(root, SETTINGS_PATH);
157
+ const text = existsSync(path) ? readFileSync(path, 'utf8') : '';
158
+ // Событие, отданное диспетчеру, подключает все свои гарды разом: их пути в настройке не
159
+ // стоят вовсе — диспетчер собирает ветки по объявлениям в шапках. Искать там имя каждого
160
+ // гарда значило бы называть неподключённым весь набор.
161
+ return bindings.filter((binding) => !dispatchesEvent(text, binding.event) && !bound.has(`${binding.event} ${binding.path}`) && !bound.has(binding.path));
130
162
  }
131
163
  /**
132
164
  * Образцы, под которыми гарды стоят в настройке агента: ключ — «событие и путь».
@@ -1 +1 @@
1
- {"version":3,"file":"hooks-map.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/hooks-map.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Карта хуков: чем разложенные гарды подключаются к агенту.\n *\n * Раскладка кладёт файлы, а зовёт их запись в настройке агента — чужом JSON, которым пакет не\n * владеет. Пока о ней молчали, свежее дерево получало два десятка гардов, ни один из которых не\n * срабатывал, и узнавало об этом, когда что-нибудь проходило мимо.\n *\n * Пакет не пишет в чужую настройку сам: файл держит проект, в нём лежит и то, о чём пакет не\n * знает вовсе, а слияние чужого JSON молча теряет несовпавшее. Вместо этого он собирает готовый\n * кусок и проверяет, что он вставлен. Это дешевле и честнее: правку в свою настройку делает тот,\n * кто за неё отвечает.\n *\n * Событие и образец вызова гард несёт сам — строкой `# rt-hook: <событие> <образец>` во второй\n * строке файла. Карта, выписанная отдельным списком, разошлась бы с набором гардов на первом же\n * добавленном, и заметить это было бы нечем: гард просто не звался бы.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { byKey } from './order.js';\n\n/** Настройка агента, в которой живёт карта. Путь от корня дерева. */\nexport const SETTINGS_PATH: string = '.claude/settings.json';\n\nconst DECLARATION: RegExp = /^#\\s*rt-hook:\\s*(\\S+)(?:[ \\t]+(\\S.*))?$/gm;\n\nexport interface IHookBinding {\n /** Событие агента: `PreToolUse`, `PostToolUse`, `SessionStart`, `Stop`. */\n readonly event: string;\n /**\n * Образец, по которому событие достаётся этому гарду. Пустой у события, которое не про\n * инструмент: завершение хода приходит целиком, и выбирать в нём нечего.\n */\n readonly matcher: string;\n /** Путь разложенного гарда от корня дерева. */\n readonly path: string;\n}\n\n/**\n * Что гард говорит о себе. Молчит — он не подключается к агенту вовсе.\n *\n * Объявлений бывает несколько: гард, который сперва напоминает, а потом отбивает, стоит на двух\n * событиях сразу. Разводить его по двум файлам значило бы держать два разбора одной записи и\n * два места, где правится один порог.\n */\nexport function bindingsOf(text: string, path: string): readonly IHookBinding[] {\n return [...text.matchAll(DECLARATION)].map((found: RegExpMatchArray): IHookBinding => ({\n event: found[1],\n matcher: (found[2] ?? '').trim(),\n path,\n }));\n}\n\n/**\n * Раздел `hooks` для настройки агента: событие → образец → команда.\n *\n * Гарды с одинаковым образцом внутри события собираются в одну запись — так же, как это пишут\n * руками, и так же, как читает агент.\n */\nexport function hooksSection(bindings: readonly IHookBinding[]): Record<string, unknown> {\n const events: Map<string, Map<string, string[]>> = new Map();\n\n for (const binding of bindings) {\n const byMatcher: Map<string, string[]> = events.get(binding.event) ?? new Map();\n byMatcher.set(binding.matcher, [...(byMatcher.get(binding.matcher) ?? []), binding.path]);\n events.set(binding.event, byMatcher);\n }\n\n const section: Record<string, unknown> = {};\n for (const [event, byMatcher] of [...events].sort(byKey)) {\n section[event] = [...byMatcher].sort(byKey).map(([matcher, paths]: [string, readonly string[]]): unknown => {\n const hooks: unknown[] = paths.map((path: string): unknown => ({\n type: 'command',\n command: `$CLAUDE_PROJECT_DIR/${path}`,\n }));\n\n // Запись без образца — не запись с пустым образцом: агент читает пустую строку как\n // образец, которому не соответствует ни один вызов, и гард молча не зовётся.\n return matcher ? { matcher, hooks } : { hooks };\n });\n }\n\n return section;\n}\n\n/** Одна команда гарда в настройке агента: событие, путь и образец, под которым она стоит. */\ninterface ISettingsHook {\n readonly event: string;\n readonly path: string;\n readonly matcher: string;\n}\n\n/** Команды одной записи настройки: у всех у них общий образец — он объявлен записью, а не командой. */\nfunction hooksOfRecord(event: string, record: unknown): readonly ISettingsHook[] {\n const asked: unknown = (record as Record<string, unknown> | null)?.['matcher'];\n const matcher: string = typeof asked === 'string' ? asked.trim() : '';\n const commands: unknown = (record as Record<string, unknown> | null)?.['hooks'];\n const found: ISettingsHook[] = [];\n\n for (const command of Array.isArray(commands) ? commands : []) {\n const line: unknown = (command as Record<string, unknown> | null)?.['command'];\n\n if (typeof line === 'string') {\n const at: number = line.indexOf('.claude/');\n\n found.push({ event, matcher, path: at < 0 ? line : line.slice(at) });\n }\n }\n\n return found;\n}\n\n/** Все команды гардов из настройки агента плоским списком. Текст не разбирается — отказ разбора. */\nfunction hooksInSettings(text: string): readonly ISettingsHook[] {\n const settings: unknown = JSON.parse(text);\n const hooks: unknown = (settings as Record<string, unknown> | null)?.['hooks'];\n const found: ISettingsHook[] = [];\n\n for (const [event, records] of Object.entries((hooks ?? {}) as Record<string, unknown>)) {\n for (const record of Array.isArray(records) ? records : []) {\n found.push(...hooksOfRecord(event, record));\n }\n }\n\n return found;\n}\n\n/** Ключ, которым гард узнаётся: событие и путь вместе — на двух событиях он стоит дважды. */\nfunction keyOf(hook: ISettingsHook): string {\n return `${hook.event} ${hook.path}`;\n}\n\n/** Пути гардов, названные в настройке агента этого дерева. Нет файла — ни одного. */\nexport function boundInSettings(root: string): readonly string[] {\n const path: string = join(root, SETTINGS_PATH);\n if (!existsSync(path)) {\n return [];\n }\n let text: string;\n try {\n text = readFileSync(path, 'utf8');\n } catch {\n return [];\n }\n\n // Сперва разбором: гард, стоящий на двух событиях, подключается к каждому отдельно, и по\n // одному имени файла этого не увидеть — подключённый к первому событию выглядел бы\n // подключённым и ко второму.\n try {\n return hooksInSettings(text).map(keyOf);\n } catch {\n // Настройку дерево вправе держать так, как ему удобно, — вплоть до комментариев, которых\n // JSON не разбирает. Тогда ищется одно: назван ли путь гарда хоть где-нибудь в ней.\n return [...text.matchAll(/\\.claude\\/hooks\\/[\\w.-]+\\.sh/g)].map((found: RegExpMatchArray): string => found[0]);\n }\n}\n\n/**\n * Разложенные гарды, которых в настройке агента нет: они лежат, но их никто не позовёт.\n *\n * Разбор настройки называет пару «событие и путь», а падение назад — один путь. Поэтому\n * подключённым считается и то, и другое: иначе дерево с настройкой, которую не разобрать,\n * получало бы список из всех гардов разом.\n */\nexport function unboundHooks(bindings: readonly IHookBinding[], root: string): readonly IHookBinding[] {\n const bound: ReadonlySet<string> = new Set(boundInSettings(root));\n\n return bindings.filter((binding: IHookBinding): boolean => !bound.has(`${binding.event} ${binding.path}`) && !bound.has(binding.path));\n}\n\n/**\n * Образцы, под которыми гарды стоят в настройке агента: ключ — «событие и путь».\n *\n * Настройку, которую не разобрать, читать здесь нечем: образец лежит полем объекта, и вынуть его\n * из текста поиском значило бы гадать. Тогда карта пуста, и расхождений не находится ни одного —\n * это честнее, чем назвать расхождением то, чего не прочитали.\n */\nexport function matchersInSettings(root: string): ReadonlyMap<string, string> {\n const path: string = join(root, SETTINGS_PATH);\n if (!existsSync(path)) {\n return new Map();\n }\n\n try {\n return new Map(\n hooksInSettings(readFileSync(path, 'utf8')).map((one: ISettingsHook): [string, string] => [keyOf(one), one.matcher])\n );\n } catch {\n return new Map();\n }\n}\n\n/** Гард, чьё объявление разошлось с образцом, под которым его зовут. */\nexport interface IMatcherDrift {\n readonly event: string;\n readonly path: string;\n /** Что гард объявляет о себе строкой `# rt-hook:`. */\n readonly declared: string;\n /** Под чем он на самом деле стоит в настройке агента. */\n readonly bound: string;\n}\n\n/**\n * Расхождения объявления гарда с образцом, под которым его зовут.\n *\n * Гард, подписанный не на то, что объявляет, хуже неподключённого: снаружи он выглядит\n * работающим — путь его в настройке назван, файл разложен, набор сценариев зелёный, — а вызов,\n * ради которого его тело и написано, до него не доходит никогда. Так гейт правил и разбирал\n * вызовы браузера веткой, которая не исполнялась ни разу: набор звал гард напрямую с\n * подставленным вводом и объявления не читал вовсе.\n *\n * Судятся только подключённые гарды: о неподключённых говорит своя строка, и назвать их дважды\n * значило бы спрятать настоящее расхождение среди повторов.\n */\nexport function driftedMatchers(bindings: readonly IHookBinding[], root: string): readonly IMatcherDrift[] {\n const bound: ReadonlyMap<string, string> = matchersInSettings(root);\n const drifts: IMatcherDrift[] = [];\n\n for (const binding of bindings) {\n const key: string = `${binding.event} ${binding.path}`;\n const inSettings: string | undefined = bound.get(key);\n if (inSettings === undefined || inSettings === binding.matcher) {\n continue;\n }\n drifts.push({ event: binding.event, path: binding.path, declared: binding.matcher, bound: inSettings });\n }\n\n return drifts;\n}\n"]}
1
+ {"version":3,"file":"hooks-map.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/hooks-map.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Карта хуков: чем разложенные гарды подключаются к агенту.\n *\n * Раскладка кладёт файлы, а зовёт их запись в настройке агента — чужом JSON, которым пакет не\n * владеет. Пока о ней молчали, свежее дерево получало два десятка гардов, ни один из которых не\n * срабатывал, и узнавало об этом, когда что-нибудь проходило мимо.\n *\n * Пакет не пишет в чужую настройку сам: файл держит проект, в нём лежит и то, о чём пакет не\n * знает вовсе, а слияние чужого JSON молча теряет несовпавшее. Вместо этого он собирает готовый\n * кусок и проверяет, что он вставлен. Это дешевле и честнее: правку в свою настройку делает тот,\n * кто за неё отвечает.\n *\n * Событие и образец вызова гард несёт сам — строкой `# rt-hook: <событие> <образец>` во второй\n * строке файла. Карта, выписанная отдельным списком, разошлась бы с набором гардов на первом же\n * добавленном, и заметить это было бы нечем: гард просто не звался бы.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { byKey } from './order.js';\n\n/** Настройка агента, в которой живёт карта. Путь от корня дерева. */\nexport const SETTINGS_PATH: string = '.claude/settings.json';\n\nconst DECLARATION: RegExp = /^#\\s*rt-hook:\\s*(\\S+)(?:[ \\t]+(\\S.*))?$/gm;\n\nexport interface IHookBinding {\n /** Событие агента: `PreToolUse`, `PostToolUse`, `SessionStart`, `Stop`. */\n readonly event: string;\n /**\n * Образец, по которому событие достаётся этому гарду. Пустой у события, которое не про\n * инструмент: завершение хода приходит целиком, и выбирать в нём нечего.\n */\n readonly matcher: string;\n /** Путь разложенного гарда от корня дерева. */\n readonly path: string;\n}\n\n/**\n * Что гард говорит о себе. Молчит — он не подключается к агенту вовсе.\n *\n * Объявлений бывает несколько: гард, который сперва напоминает, а потом отбивает, стоит на двух\n * событиях сразу. Разводить его по двум файлам значило бы держать два разбора одной записи и\n * два места, где правится один порог.\n */\nexport function bindingsOf(text: string, path: string): readonly IHookBinding[] {\n return [...text.matchAll(DECLARATION)].map((found: RegExpMatchArray): IHookBinding => ({\n event: found[1],\n matcher: (found[2] ?? '').trim(),\n path,\n }));\n}\n\n/**\n * Раздел `hooks` для настройки агента: событие → образец → команда.\n *\n * Гарды с одинаковым образцом внутри события собираются в одну запись — так же, как это пишут\n * руками, и так же, как читает агент.\n */\n/** Диспетчер событий: одна запись в настройке вместо списка гардов. Путь от корня дерева. */\nexport const DISPATCH_PATH: string = '.claude/hooks/dispatch.sh';\n\n/**\n * Стоит ли в настройке диспетчер этого события. Он зовётся с именем события доводом, поэтому\n * найденный путь без довода — не он: так стояла бы запись, зовущая диспетчер на всём подряд.\n */\nfunction dispatchesEvent(text: string, event: string): boolean {\n return text.includes(`${DISPATCH_PATH} ${event}`);\n}\n\n/**\n * Кусок настройки агента: по строке на событие, и каждая зовёт диспетчер с именем события.\n *\n * Прежде здесь стоял список гардов — по записи на образец вызова, — и агент запускал каждый\n * своим процессом, подавая всем один и тот же ввод. Восемнадцать гардов на вызове инструмента\n * разбирали его восемнадцать раз; замер дерева, где это считали, дал восемьсот двадцать шесть\n * миллисекунд на вызов против четырёхсот сорока одной после. Образец вызова никуда не делся:\n * его по-прежнему несёт шапка самого гарда, только сверяет его теперь диспетчер, а не агент.\n */\nexport function hooksSection(bindings: readonly IHookBinding[]): Record<string, unknown> {\n const events: Set<string> = new Set(bindings.map((binding: IHookBinding): string => binding.event));\n const section: Record<string, unknown> = {};\n\n for (const event of [...events].sort((left: string, right: string): number => left.localeCompare(right))) {\n section[event] = [{ hooks: [{ type: 'command', command: `$CLAUDE_PROJECT_DIR/${DISPATCH_PATH} ${event}` }] }];\n }\n\n return section;\n}\n\n/** Прежний вид куска настройки: по записи на образец вызова. Остаётся ради разбора чужих настроек. */\nexport function hooksSectionByMatcher(bindings: readonly IHookBinding[]): Record<string, unknown> {\n const events: Map<string, Map<string, string[]>> = new Map();\n\n for (const binding of bindings) {\n const byMatcher: Map<string, string[]> = events.get(binding.event) ?? new Map();\n byMatcher.set(binding.matcher, [...(byMatcher.get(binding.matcher) ?? []), binding.path]);\n events.set(binding.event, byMatcher);\n }\n\n const section: Record<string, unknown> = {};\n for (const [event, byMatcher] of [...events].sort(byKey)) {\n section[event] = [...byMatcher].sort(byKey).map(([matcher, paths]: [string, readonly string[]]): unknown => {\n const hooks: unknown[] = paths.map((path: string): unknown => ({\n type: 'command',\n command: `$CLAUDE_PROJECT_DIR/${path}`,\n }));\n\n // Запись без образца — не запись с пустым образцом: агент читает пустую строку как\n // образец, которому не соответствует ни один вызов, и гард молча не зовётся.\n return matcher ? { matcher, hooks } : { hooks };\n });\n }\n\n return section;\n}\n\n/** Одна команда гарда в настройке агента: событие, путь и образец, под которым она стоит. */\ninterface ISettingsHook {\n readonly event: string;\n readonly path: string;\n readonly matcher: string;\n}\n\n/** Команды одной записи настройки: у всех у них общий образец — он объявлен записью, а не командой. */\nfunction hooksOfRecord(event: string, record: unknown): readonly ISettingsHook[] {\n const asked: unknown = (record as Record<string, unknown> | null)?.['matcher'];\n const matcher: string = typeof asked === 'string' ? asked.trim() : '';\n const commands: unknown = (record as Record<string, unknown> | null)?.['hooks'];\n const found: ISettingsHook[] = [];\n\n for (const command of Array.isArray(commands) ? commands : []) {\n const line: unknown = (command as Record<string, unknown> | null)?.['command'];\n\n if (typeof line === 'string') {\n const at: number = line.indexOf('.claude/');\n\n found.push({ event, matcher, path: at < 0 ? line : line.slice(at) });\n }\n }\n\n return found;\n}\n\n/** Все команды гардов из настройки агента плоским списком. Текст не разбирается — отказ разбора. */\nfunction hooksInSettings(text: string): readonly ISettingsHook[] {\n const settings: unknown = JSON.parse(text);\n const hooks: unknown = (settings as Record<string, unknown> | null)?.['hooks'];\n const found: ISettingsHook[] = [];\n\n for (const [event, records] of Object.entries((hooks ?? {}) as Record<string, unknown>)) {\n for (const record of Array.isArray(records) ? records : []) {\n found.push(...hooksOfRecord(event, record));\n }\n }\n\n return found;\n}\n\n/** Ключ, которым гард узнаётся: событие и путь вместе — на двух событиях он стоит дважды. */\nfunction keyOf(hook: ISettingsHook): string {\n return `${hook.event} ${hook.path}`;\n}\n\n/** Пути гардов, названные в настройке агента этого дерева. Нет файла — ни одного. */\nexport function boundInSettings(root: string): readonly string[] {\n const path: string = join(root, SETTINGS_PATH);\n if (!existsSync(path)) {\n return [];\n }\n let text: string;\n try {\n text = readFileSync(path, 'utf8');\n } catch {\n return [];\n }\n\n // Сперва разбором: гард, стоящий на двух событиях, подключается к каждому отдельно, и по\n // одному имени файла этого не увидеть — подключённый к первому событию выглядел бы\n // подключённым и ко второму.\n try {\n return hooksInSettings(text).map(keyOf);\n } catch {\n // Настройку дерево вправе держать так, как ему удобно, — вплоть до комментариев, которых\n // JSON не разбирает. Тогда ищется одно: назван ли путь гарда хоть где-нибудь в ней.\n return [...text.matchAll(/\\.claude\\/hooks\\/[\\w.-]+\\.sh/g)].map((found: RegExpMatchArray): string => found[0]);\n }\n}\n\n/**\n * Разложенные гарды, которых в настройке агента нет: они лежат, но их никто не позовёт.\n *\n * Разбор настройки называет пару «событие и путь», а падение назад — один путь. Поэтому\n * подключённым считается и то, и другое: иначе дерево с настройкой, которую не разобрать,\n * получало бы список из всех гардов разом.\n */\nexport function unboundHooks(bindings: readonly IHookBinding[], root: string): readonly IHookBinding[] {\n const bound: ReadonlySet<string> = new Set(boundInSettings(root));\n const path: string = join(root, SETTINGS_PATH);\n const text: string = existsSync(path) ? readFileSync(path, 'utf8') : '';\n\n // Событие, отданное диспетчеру, подключает все свои гарды разом: их пути в настройке не\n // стоят вовсе — диспетчер собирает ветки по объявлениям в шапках. Искать там имя каждого\n // гарда значило бы называть неподключённым весь набор.\n return bindings.filter(\n (binding: IHookBinding): boolean =>\n !dispatchesEvent(text, binding.event) && !bound.has(`${binding.event} ${binding.path}`) && !bound.has(binding.path)\n );\n}\n\n/**\n * Образцы, под которыми гарды стоят в настройке агента: ключ — «событие и путь».\n *\n * Настройку, которую не разобрать, читать здесь нечем: образец лежит полем объекта, и вынуть его\n * из текста поиском значило бы гадать. Тогда карта пуста, и расхождений не находится ни одного —\n * это честнее, чем назвать расхождением то, чего не прочитали.\n */\nexport function matchersInSettings(root: string): ReadonlyMap<string, string> {\n const path: string = join(root, SETTINGS_PATH);\n if (!existsSync(path)) {\n return new Map();\n }\n\n try {\n return new Map(\n hooksInSettings(readFileSync(path, 'utf8')).map((one: ISettingsHook): [string, string] => [keyOf(one), one.matcher])\n );\n } catch {\n return new Map();\n }\n}\n\n/** Гард, чьё объявление разошлось с образцом, под которым его зовут. */\nexport interface IMatcherDrift {\n readonly event: string;\n readonly path: string;\n /** Что гард объявляет о себе строкой `# rt-hook:`. */\n readonly declared: string;\n /** Под чем он на самом деле стоит в настройке агента. */\n readonly bound: string;\n}\n\n/**\n * Расхождения объявления гарда с образцом, под которым его зовут.\n *\n * Гард, подписанный не на то, что объявляет, хуже неподключённого: снаружи он выглядит\n * работающим — путь его в настройке назван, файл разложен, набор сценариев зелёный, — а вызов,\n * ради которого его тело и написано, до него не доходит никогда. Так гейт правил и разбирал\n * вызовы браузера веткой, которая не исполнялась ни разу: набор звал гард напрямую с\n * подставленным вводом и объявления не читал вовсе.\n *\n * Судятся только подключённые гарды: о неподключённых говорит своя строка, и назвать их дважды\n * значило бы спрятать настоящее расхождение среди повторов.\n */\nexport function driftedMatchers(bindings: readonly IHookBinding[], root: string): readonly IMatcherDrift[] {\n const bound: ReadonlyMap<string, string> = matchersInSettings(root);\n const drifts: IMatcherDrift[] = [];\n\n for (const binding of bindings) {\n const key: string = `${binding.event} ${binding.path}`;\n const inSettings: string | undefined = bound.get(key);\n if (inSettings === undefined || inSettings === binding.matcher) {\n continue;\n }\n drifts.push({ event: binding.event, path: binding.path, declared: binding.matcher, bound: inSettings });\n }\n\n return drifts;\n}\n"]}
@@ -11,6 +11,8 @@ export interface IProposal {
11
11
  readonly body: string;
12
12
  /** Ссылка на заведённую запись; пусто — не отправлялось. */
13
13
  readonly sent: string;
14
+ /** Поле ближайшего утверждения, как его написали; пусто — поля нет вовсе. */
15
+ readonly nearest: string;
14
16
  /** Файл, в котором блок лежит, путём от корня дерева. */
15
17
  readonly file: string;
16
18
  /** Строка заголовка в файле, считая с единицы. */
@@ -43,6 +45,8 @@ export declare function leaksIn(text: string, marks: readonly string[]): readonl
43
45
  * встречается чаще, чем путь целиком.
44
46
  */
45
47
  export declare function marksOf(root: string, remote: string): readonly string[];
48
+ /** Почему блок не уезжает; пустая строка — уезжает. */
49
+ export declare function nearestMissing(proposal: IProposal, assetsDir: string): string;
46
50
  /**
47
51
  * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.
48
52
  *
@@ -52,5 +56,5 @@ export declare function marksOf(root: string, remote: string): readonly string[]
52
56
  * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся
53
57
  * вовсе, и при следующей отправке он уезжает вторым разом.
54
58
  */
55
- export declare function markSent(text: string, proposal: IProposal, url: string): string;
59
+ export declare function markSent(text: string, proposal: IProposal, url: string, field?: string): string;
56
60
  //# sourceMappingURL=proposals.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"proposals.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"AAeA,mEAAmE;AACnE,eAAO,MAAM,aAAa,EAAE,MAAmC,CAAC;AAEhE,yEAAyE;AACzE,eAAO,MAAM,UAAU,EAAE,MAAgB,CAAC;AAC1C,eAAO,MAAM,YAAY,EAAE,MAAoB,CAAC;AAChD,eAAO,MAAM,OAAO,EAAE,MAAiB,CAAC;AAqCxC,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAgC/E;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAUhE;AAED,MAAM,WAAW,KAAK;IAClB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAKD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,KAAK,EAAE,CAgBhF;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAIvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ/E"}
1
+ {"version":3,"file":"proposals.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"AAeA,mEAAmE;AACnE,eAAO,MAAM,aAAa,EAAE,MAAmC,CAAC;AAEhE,yEAAyE;AACzE,eAAO,MAAM,UAAU,EAAE,MAAgB,CAAC;AAC1C,eAAO,MAAM,YAAY,EAAE,MAAoB,CAAC;AAChD,eAAO,MAAM,OAAO,EAAE,MAAiB,CAAC;AA2ExC,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAwC/E;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAUhE;AAED,MAAM,WAAW,KAAK;IAClB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAKD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,KAAK,EAAE,CAgBhF;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAIvE;AAOD,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAuB7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,MAAqB,GAAG,MAAM,CAU7G"}
package/lib/proposals.js CHANGED
@@ -39,7 +39,40 @@ function headingOf(line) {
39
39
  return address && !address.includes(' ') && resource ? { address, resource } : null;
40
40
  }
41
41
  /** Пометка об отправке. По ней же предложение узнаётся отправленным. */
42
- const SENT = /^-\s+\*\*отправлено:\*\*\s*(\S+)/m;
42
+ const SENT = /^- +\*\*отправлено:\*\* *(\S+)/m;
43
+ /**
44
+ * Поле ближайшего утверждения: точная цитата строки ресурса, к которой блок относится, — либо
45
+ * слово о том, что ближайшего нет вовсе.
46
+ *
47
+ * Поле обязательно, и обязательно оно затем, чтобы ресурс был прочитан. Мерить похожесть текстов
48
+ * пробовали замером: законное соседство двух статей одного правила дало 0.345 общих значимых
49
+ * слов, а законный перенос удачной статьи на соседнее место — 0.355. Порога между ними нет, и
50
+ * назначенный наугад он отбивал бы правки вместо повторов. Названная цитата судится фактом: она
51
+ * в ресурсе либо есть, либо её там нет.
52
+ */
53
+ const NEAREST = /^- +\*\*ближайшее:\*\* *([^\n]+)/m;
54
+ /**
55
+ * Слово о том, что ближайшего утверждения в ресурсе нет: цитировать нечего.
56
+ *
57
+ * Конец слова здесь проверяется отрицательным просмотром, а не границей слова: границу движок
58
+ * считает по латинице и цифрам, и между «т» и пробелом её нет вовсе — образец с `\b` не совпал
59
+ * бы ни с одним русским словом.
60
+ */
61
+ const NOTHING_NEAR = /^нет(?![а-яё])/i;
62
+ /**
63
+ * Цитата ближайшего утверждения: кавычки-ёлочки, как в остальных текстах дерева.
64
+ *
65
+ * Ищется позициями, а не образцом: образец на «открыть, набрать не-закрывающих, закрыть» линтер
66
+ * отбивает как ветвящийся, и поводы у него есть — строка без закрывающей кавычки перебирается им
67
+ * до конца.
68
+ */
69
+ function quotedIn(text) {
70
+ const from = text.indexOf('«');
71
+ const to = from < 0 ? -1 : text.indexOf('»', from + 1);
72
+ return from >= 0 && to > from ? text.slice(from + 1, to) : '';
73
+ }
74
+ /** Пометка любого рода: по ней считается сдвиг, когда в файл вставляют ещё одну. */
75
+ const MARKED = /^- +\*\*(отправлено|отбито):\*\*/m;
43
76
  /**
44
77
  * Блоки файла предложений.
45
78
  *
@@ -61,7 +94,15 @@ export function parseProposals(text, file) {
61
94
  .trim();
62
95
  const { address, resource } = heading;
63
96
  if (ADDRESSES.includes(address) && !resource.includes('<')) {
64
- found.push({ address, resource, body, file, sent: SENT.exec(body)?.[1] ?? '', line: at + 1 });
97
+ found.push({
98
+ address,
99
+ resource,
100
+ body,
101
+ file,
102
+ sent: SENT.exec(body)?.[1] ?? '',
103
+ nearest: (NEAREST.exec(body)?.[1] ?? '').trim(),
104
+ line: at + 1,
105
+ });
65
106
  }
66
107
  at = -1;
67
108
  };
@@ -117,6 +158,32 @@ export function marksOf(root, remote) {
117
158
  const name = basename(root);
118
159
  return [root, name, remote].filter(Boolean);
119
160
  }
161
+ /** Один пробел вместо любой пробельной вереницы: цитата в блоке перенесена по своей ширине. */
162
+ function flat(text) {
163
+ return text.replace(/\s+/g, ' ').trim();
164
+ }
165
+ /** Почему блок не уезжает; пустая строка — уезжает. */
166
+ export function nearestMissing(proposal, assetsDir) {
167
+ if (!proposal.nearest) {
168
+ return 'поля «ближайшее» нет: назови точную строку ресурса, к которой это относится, и то, чего она не покрывает, — либо напиши «нет»';
169
+ }
170
+ if (NOTHING_NEAR.test(proposal.nearest)) {
171
+ return '';
172
+ }
173
+ const quoted = quotedIn(proposal.nearest);
174
+ if (!quoted) {
175
+ return 'ближайшее названо без цитаты: строка ресурса приводится в кавычках-ёлочках дословно — иначе проверить нечего';
176
+ }
177
+ const path = join(assetsDir, proposal.resource);
178
+ if (!existsSync(path)) {
179
+ // Ресурса у этого дерева нет — сверять не с чем, и отбивать нечего: блок про ресурс,
180
+ // которого пакет здесь не держит, судится на приёмной стороне.
181
+ return '';
182
+ }
183
+ return flat(readFileSync(path, 'utf8')).includes(flat(quoted))
184
+ ? ''
185
+ : `цитаты нет в «${proposal.resource}»: ${flat(quoted).slice(0, 60)}… — либо ресурс не читали, либо утверждение переписано с тех пор`;
186
+ }
120
187
  /**
121
188
  * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.
122
189
  *
@@ -126,12 +193,14 @@ export function marksOf(root, remote) {
126
193
  * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся
127
194
  * вовсе, и при следующей отправке он уезжает вторым разом.
128
195
  */
129
- export function markSent(text, proposal, url) {
196
+ export function markSent(text, proposal, url, field = 'отправлено') {
130
197
  const lines = text.split('\n');
131
- const shift = lines.slice(0, proposal.line).filter((line) => SENT.test(line)).length;
198
+ // Сдвиг считает пометки обоих родов: отбитый блок получает свою, и следующая за ним встала бы
199
+ // строкой выше своего места, если бы её не посчитали.
200
+ const shift = lines.slice(0, proposal.line).filter((line) => MARKED.test(line)).length;
132
201
  // Пометка встаёт сразу под заголовок: конец блока определяется следующим заголовком, а его
133
202
  // может и не быть — тогда «конец» пришлось бы искать по пустым строкам в хвосте файла.
134
- lines.splice(proposal.line + shift, 0, `- **отправлено:** ${url}`);
203
+ lines.splice(proposal.line + shift, 0, `- **${field}:** ${url}`);
135
204
  return lines.join('\n');
136
205
  }
137
206
  //# sourceMappingURL=proposals.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"proposals.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Предложения по слою правил: разбор файла, отбор того, что уезжает наружу, и проверка на адрес\n * дерева.\n *\n * Файл пишет главный агент после разбора закрытой задачи — по блоку на предложение, с адресом в\n * заголовке. Адрес роль проставляет и так; здесь он впервые становится тем, что читает машина.\n *\n * Проверка на утечку живёт в этом же модуле не для удобства: предложение уезжает в чужой\n * репозиторий целиком, и запрет называть чужое дерево обязан держаться проверкой, а не памятью\n * того, кто пишет.\n */\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда главный агент кладёт предложения. Путь от корня дерева. */\nexport const PROPOSALS_DIR: string = '.claude/rt-kit/proposals';\n\n/** Адрес предложения: куда правка идёт. Наружу уезжает только первый. */\nexport const TO_PACKAGE: string = 'пакет';\nexport const TO_COMPANION: string = 'компаньон';\nexport const TO_TREE: string = 'дерево';\n\nconst ADDRESSES: readonly string[] = [TO_PACKAGE, TO_COMPANION, TO_TREE];\n\n/** Разобранный заголовок блока. */\ninterface IHeading {\n readonly address: string;\n readonly resource: string;\n}\n\n/**\n * Заголовок блока: `## <адрес> · <ресурс>`. Разделитель — тот же, что в шаблоне.\n *\n * Разбирается строкой, а не образцом: образец на «слово, разделитель, остаток» перебирает\n * границу слова столько раз, сколько в строке знаков.\n */\nfunction headingOf(line: string): IHeading | null {\n if (!line.startsWith('## ')) {\n return null;\n }\n\n const rest: string = line.slice(3);\n const at: number = rest.indexOf('·');\n\n if (at < 0) {\n return null;\n }\n\n const address: string = rest.slice(0, at).trim();\n const resource: string = rest.slice(at + 1).trim();\n\n return address && !address.includes(' ') && resource ? { address, resource } : null;\n}\n\n/** Пометка об отправке. По ней же предложение узнаётся отправленным. */\nconst SENT: RegExp = /^-\\s+\\*\\*отправлено:\\*\\*\\s*(\\S+)/m;\n\nexport interface IProposal {\n readonly address: string;\n readonly resource: string;\n /** Тело блока без заголовка: место, повод и готовый текст. */\n readonly body: string;\n /** Ссылка на заведённую запись; пусто — не отправлялось. */\n readonly sent: string;\n /** Файл, в котором блок лежит, путём от корня дерева. */\n readonly file: string;\n /** Строка заголовка в файле, считая с единицы. */\n readonly line: number;\n}\n\n/**\n * Блоки файла предложений.\n *\n * Незаполненный образец пропускается: в шаблоне ресурс стоит скобками — `rules/<правило>.md`, —\n * и отправить его значило бы завести запись о правке несуществующего файла.\n */\nexport function parseProposals(text: string, file: string): readonly IProposal[] {\n const lines: readonly string[] = text.split('\\n');\n const found: IProposal[] = [];\n let at: number = -1;\n\n const close: (end: number) => void = (end: number): void => {\n if (at < 0) {\n return;\n }\n const heading: IHeading = headingOf(lines[at]) as IHeading;\n const body: string = lines\n .slice(at + 1, end)\n .join('\\n')\n .trim();\n const { address, resource }: IHeading = heading;\n if (ADDRESSES.includes(address) && !resource.includes('<')) {\n found.push({ address, resource, body, file, sent: SENT.exec(body)?.[1] ?? '', line: at + 1 });\n }\n at = -1;\n };\n\n lines.forEach((line: string, index: number): void => {\n if (line.startsWith('## ')) {\n close(index);\n if (headingOf(line) !== null) {\n at = index;\n }\n }\n });\n close(lines.length);\n\n return found;\n}\n\n/** Все файлы предложений дерева, в порядке имён. */\nexport function readProposals(root: string): readonly IProposal[] {\n const dir: string = join(root, PROPOSALS_DIR);\n if (!existsSync(dir)) {\n return [];\n }\n\n return readdirSync(dir)\n .filter((name: string): boolean => name.endsWith('.md'))\n .sort(byText)\n .flatMap((name: string): readonly IProposal[] => parseProposals(readFileSync(join(dir, name), 'utf8'), join(PROPOSALS_DIR, name)));\n}\n\nexport interface ILeak {\n /** Строка блока, считая с единицы от заголовка. */\n readonly line: number;\n readonly text: string;\n readonly why: string;\n}\n\n/** Абсолютный путь машины: он не бывает общим ни для одного дерева, кроме этого. */\nconst ABSOLUTE: RegExp = /(^|[\\s(`'\"])(~\\/|\\/(Users|home|var|opt|srv|mnt)\\/)\\S+/;\n\n/**\n * Что в тексте называет дерево, из которого предложение уезжает.\n *\n * `marks` — имена, которые знает только это дерево: корень, его последнее звено, адрес его\n * удалённого репозитория. Пакет их не угадывает — они приходят доводом с края.\n */\nexport function leaksIn(text: string, marks: readonly string[]): readonly ILeak[] {\n const found: ILeak[] = [];\n\n text.split('\\n').forEach((line: string, index: number): void => {\n if (ABSOLUTE.test(line)) {\n found.push({ line: index + 1, text: line.trim(), why: 'абсолютный путь этой машины' });\n\n return;\n }\n const mark: string | undefined = marks.find((value: string): boolean => Boolean(value) && line.includes(value));\n if (mark) {\n found.push({ line: index + 1, text: line.trim(), why: `имя этого дерева: ${mark}` });\n }\n });\n\n return found;\n}\n\n/**\n * Чем дерево себя выдаёт. Последнее звено пути берётся отдельно от полного: в тексте оно\n * встречается чаще, чем путь целиком.\n */\nexport function marksOf(root: string, remote: string): readonly string[] {\n const name: string = basename(root);\n\n return [root, name, remote].filter(Boolean);\n}\n\n/**\n * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.\n *\n * Место считается от номера строки заголовка — но номера у всех блоков посчитаны при разборе, до\n * единой вставки, а каждая вставка сдвигает файл на строку вниз. Поэтому к номеру прибавляется\n * число пометок, уже стоящих выше: без этого вторая пометка встаёт на строку выше своего места,\n * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся\n * вовсе, и при следующей отправке он уезжает вторым разом.\n */\nexport function markSent(text: string, proposal: IProposal, url: string): string {\n const lines: string[] = text.split('\\n');\n const shift: number = lines.slice(0, proposal.line).filter((line: string): boolean => SENT.test(line)).length;\n // Пометка встаёт сразу под заголовок: конец блока определяется следующим заголовком, а его\n // может и не быть — тогда «конец» пришлось бы искать по пустым строкам в хвосте файла.\n lines.splice(proposal.line + shift, 0, `- **отправлено:** ${url}`);\n\n return lines.join('\\n');\n}\n"]}
1
+ {"version":3,"file":"proposals.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Предложения по слою правил: разбор файла, отбор того, что уезжает наружу, и проверка на адрес\n * дерева.\n *\n * Файл пишет главный агент после разбора закрытой задачи — по блоку на предложение, с адресом в\n * заголовке. Адрес роль проставляет и так; здесь он впервые становится тем, что читает машина.\n *\n * Проверка на утечку живёт в этом же модуле не для удобства: предложение уезжает в чужой\n * репозиторий целиком, и запрет называть чужое дерево обязан держаться проверкой, а не памятью\n * того, кто пишет.\n */\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда главный агент кладёт предложения. Путь от корня дерева. */\nexport const PROPOSALS_DIR: string = '.claude/rt-kit/proposals';\n\n/** Адрес предложения: куда правка идёт. Наружу уезжает только первый. */\nexport const TO_PACKAGE: string = 'пакет';\nexport const TO_COMPANION: string = 'компаньон';\nexport const TO_TREE: string = 'дерево';\n\nconst ADDRESSES: readonly string[] = [TO_PACKAGE, TO_COMPANION, TO_TREE];\n\n/** Разобранный заголовок блока. */\ninterface IHeading {\n readonly address: string;\n readonly resource: string;\n}\n\n/**\n * Заголовок блока: `## <адрес> · <ресурс>`. Разделитель — тот же, что в шаблоне.\n *\n * Разбирается строкой, а не образцом: образец на «слово, разделитель, остаток» перебирает\n * границу слова столько раз, сколько в строке знаков.\n */\nfunction headingOf(line: string): IHeading | null {\n if (!line.startsWith('## ')) {\n return null;\n }\n\n const rest: string = line.slice(3);\n const at: number = rest.indexOf('·');\n\n if (at < 0) {\n return null;\n }\n\n const address: string = rest.slice(0, at).trim();\n const resource: string = rest.slice(at + 1).trim();\n\n return address && !address.includes(' ') && resource ? { address, resource } : null;\n}\n\n/** Пометка об отправке. По ней же предложение узнаётся отправленным. */\nconst SENT: RegExp = /^- +\\*\\*отправлено:\\*\\* *(\\S+)/m;\n\n/**\n * Поле ближайшего утверждения: точная цитата строки ресурса, к которой блок относится, — либо\n * слово о том, что ближайшего нет вовсе.\n *\n * Поле обязательно, и обязательно оно затем, чтобы ресурс был прочитан. Мерить похожесть текстов\n * пробовали замером: законное соседство двух статей одного правила дало 0.345 общих значимых\n * слов, а законный перенос удачной статьи на соседнее место — 0.355. Порога между ними нет, и\n * назначенный наугад он отбивал бы правки вместо повторов. Названная цитата судится фактом: она\n * в ресурсе либо есть, либо её там нет.\n */\nconst NEAREST: RegExp = /^- +\\*\\*ближайшее:\\*\\* *([^\\n]+)/m;\n\n/**\n * Слово о том, что ближайшего утверждения в ресурсе нет: цитировать нечего.\n *\n * Конец слова здесь проверяется отрицательным просмотром, а не границей слова: границу движок\n * считает по латинице и цифрам, и между «т» и пробелом её нет вовсе — образец с `\\b` не совпал\n * бы ни с одним русским словом.\n */\nconst NOTHING_NEAR: RegExp = /^нет(?![а-яё])/i;\n\n/**\n * Цитата ближайшего утверждения: кавычки-ёлочки, как в остальных текстах дерева.\n *\n * Ищется позициями, а не образцом: образец на «открыть, набрать не-закрывающих, закрыть» линтер\n * отбивает как ветвящийся, и поводы у него есть — строка без закрывающей кавычки перебирается им\n * до конца.\n */\nfunction quotedIn(text: string): string {\n const from: number = text.indexOf('«');\n const to: number = from < 0 ? -1 : text.indexOf('»', from + 1);\n\n return from >= 0 && to > from ? text.slice(from + 1, to) : '';\n}\n\n/** Пометка любого рода: по ней считается сдвиг, когда в файл вставляют ещё одну. */\nconst MARKED: RegExp = /^- +\\*\\*(отправлено|отбито):\\*\\*/m;\n\nexport interface IProposal {\n readonly address: string;\n readonly resource: string;\n /** Тело блока без заголовка: место, повод и готовый текст. */\n readonly body: string;\n /** Ссылка на заведённую запись; пусто — не отправлялось. */\n readonly sent: string;\n /** Поле ближайшего утверждения, как его написали; пусто — поля нет вовсе. */\n readonly nearest: string;\n /** Файл, в котором блок лежит, путём от корня дерева. */\n readonly file: string;\n /** Строка заголовка в файле, считая с единицы. */\n readonly line: number;\n}\n\n/**\n * Блоки файла предложений.\n *\n * Незаполненный образец пропускается: в шаблоне ресурс стоит скобками — `rules/<правило>.md`, —\n * и отправить его значило бы завести запись о правке несуществующего файла.\n */\nexport function parseProposals(text: string, file: string): readonly IProposal[] {\n const lines: readonly string[] = text.split('\\n');\n const found: IProposal[] = [];\n let at: number = -1;\n\n const close: (end: number) => void = (end: number): void => {\n if (at < 0) {\n return;\n }\n const heading: IHeading = headingOf(lines[at]) as IHeading;\n const body: string = lines\n .slice(at + 1, end)\n .join('\\n')\n .trim();\n const { address, resource }: IHeading = heading;\n if (ADDRESSES.includes(address) && !resource.includes('<')) {\n found.push({\n address,\n resource,\n body,\n file,\n sent: SENT.exec(body)?.[1] ?? '',\n nearest: (NEAREST.exec(body)?.[1] ?? '').trim(),\n line: at + 1,\n });\n }\n at = -1;\n };\n\n lines.forEach((line: string, index: number): void => {\n if (line.startsWith('## ')) {\n close(index);\n if (headingOf(line) !== null) {\n at = index;\n }\n }\n });\n close(lines.length);\n\n return found;\n}\n\n/** Все файлы предложений дерева, в порядке имён. */\nexport function readProposals(root: string): readonly IProposal[] {\n const dir: string = join(root, PROPOSALS_DIR);\n if (!existsSync(dir)) {\n return [];\n }\n\n return readdirSync(dir)\n .filter((name: string): boolean => name.endsWith('.md'))\n .sort(byText)\n .flatMap((name: string): readonly IProposal[] => parseProposals(readFileSync(join(dir, name), 'utf8'), join(PROPOSALS_DIR, name)));\n}\n\nexport interface ILeak {\n /** Строка блока, считая с единицы от заголовка. */\n readonly line: number;\n readonly text: string;\n readonly why: string;\n}\n\n/** Абсолютный путь машины: он не бывает общим ни для одного дерева, кроме этого. */\nconst ABSOLUTE: RegExp = /(^|[\\s(`'\"])(~\\/|\\/(Users|home|var|opt|srv|mnt)\\/)\\S+/;\n\n/**\n * Что в тексте называет дерево, из которого предложение уезжает.\n *\n * `marks` — имена, которые знает только это дерево: корень, его последнее звено, адрес его\n * удалённого репозитория. Пакет их не угадывает — они приходят доводом с края.\n */\nexport function leaksIn(text: string, marks: readonly string[]): readonly ILeak[] {\n const found: ILeak[] = [];\n\n text.split('\\n').forEach((line: string, index: number): void => {\n if (ABSOLUTE.test(line)) {\n found.push({ line: index + 1, text: line.trim(), why: 'абсолютный путь этой машины' });\n\n return;\n }\n const mark: string | undefined = marks.find((value: string): boolean => Boolean(value) && line.includes(value));\n if (mark) {\n found.push({ line: index + 1, text: line.trim(), why: `имя этого дерева: ${mark}` });\n }\n });\n\n return found;\n}\n\n/**\n * Чем дерево себя выдаёт. Последнее звено пути берётся отдельно от полного: в тексте оно\n * встречается чаще, чем путь целиком.\n */\nexport function marksOf(root: string, remote: string): readonly string[] {\n const name: string = basename(root);\n\n return [root, name, remote].filter(Boolean);\n}\n\n/** Один пробел вместо любой пробельной вереницы: цитата в блоке перенесена по своей ширине. */\nfunction flat(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n/** Почему блок не уезжает; пустая строка — уезжает. */\nexport function nearestMissing(proposal: IProposal, assetsDir: string): string {\n if (!proposal.nearest) {\n return 'поля «ближайшее» нет: назови точную строку ресурса, к которой это относится, и то, чего она не покрывает, — либо напиши «нет»';\n }\n if (NOTHING_NEAR.test(proposal.nearest)) {\n return '';\n }\n\n const quoted: string = quotedIn(proposal.nearest);\n if (!quoted) {\n return 'ближайшее названо без цитаты: строка ресурса приводится в кавычках-ёлочках дословно — иначе проверить нечего';\n }\n\n const path: string = join(assetsDir, proposal.resource);\n if (!existsSync(path)) {\n // Ресурса у этого дерева нет — сверять не с чем, и отбивать нечего: блок про ресурс,\n // которого пакет здесь не держит, судится на приёмной стороне.\n return '';\n }\n\n return flat(readFileSync(path, 'utf8')).includes(flat(quoted))\n ? ''\n : `цитаты нет в «${proposal.resource}»: ${flat(quoted).slice(0, 60)}… — либо ресурс не читали, либо утверждение переписано с тех пор`;\n}\n\n/**\n * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.\n *\n * Место считается от номера строки заголовка — но номера у всех блоков посчитаны при разборе, до\n * единой вставки, а каждая вставка сдвигает файл на строку вниз. Поэтому к номеру прибавляется\n * число пометок, уже стоящих выше: без этого вторая пометка встаёт на строку выше своего места,\n * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся\n * вовсе, и при следующей отправке он уезжает вторым разом.\n */\nexport function markSent(text: string, proposal: IProposal, url: string, field: string = 'отправлено'): string {\n const lines: string[] = text.split('\\n');\n // Сдвиг считает пометки обоих родов: отбитый блок получает свою, и следующая за ним встала бы\n // строкой выше своего места, если бы её не посчитали.\n const shift: number = lines.slice(0, proposal.line).filter((line: string): boolean => MARKED.test(line)).length;\n // Пометка встаёт сразу под заголовок: конец блока определяется следующим заголовком, а его\n // может и не быть — тогда «конец» пришлось бы искать по пустым строкам в хвосте файла.\n lines.splice(proposal.line + shift, 0, `- **${field}:** ${url}`);\n\n return lines.join('\\n');\n}\n"]}
package/lib/ship.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { IIntakeAccepted } from './cargo.js';
2
- import { TMarkCall } from './cargo-state.js';
3
2
  /**
4
3
  * Предел ожидания приёма. Без него молчащая — не отказавшая — служба держит отправку до
5
4
  * умолчания среды, а это минуты: человек, позвавший команду, видит не отказ, а зависший запуск.
@@ -37,6 +36,6 @@ export declare function intakeUrl(intake: string, operation: string): string;
37
36
  export declare function readToken(root: string, spoken: string): string;
38
37
  /** Запрос приёма. Токен уезжает заголовком и в теле не появляется ни разу. */
39
38
  export declare const httpShip: TShip;
39
+ /** Два числа и отбитые строки из ответа приёма. Ответ не тем — пусто. */
40
40
  /** Запрос правки состояния. Токен уезжает заголовком и в теле не появляется ни разу. */
41
- export declare const httpMark: TMarkCall;
42
41
  //# sourceMappingURL=ship.d.ts.map
package/lib/ship.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ship.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/ship.ts"],"names":[],"mappings":"AAcA,OAAO,EAA2D,eAAe,EAAsC,MAAM,YAAY,CAAC;AAC1I,OAAO,EAAW,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAe,CAAC;AAE9C,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACtB,gGAAgG;IAChG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CAC1B;AAED,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,0FAA0F;IAC1F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;CAC7C;AAED,wDAAwD;AACxD,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAa9F,qFAAqF;AACrF,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQ9D;AAqCD,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,EAAE,KAiBtB,CAAC;AAwCF,wFAAwF;AACxF,eAAO,MAAM,QAAQ,EAAE,SAsBtB,CAAC"}
1
+ {"version":3,"file":"ship.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/ship.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,eAAe,EAAqB,MAAM,YAAY,CAAC;AAEhE;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAAe,CAAC;AAE9C,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACtB,gGAAgG;IAChG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CAC1B;AAED,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,0FAA0F;IAC1F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uFAAuF;IACvF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;CAC7C;AAED,wDAAwD;AACxD,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAa9F,qFAAqF;AACrF,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQ9D;AAqCD,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,EAAE,KAiBtB,CAAC;AAEF,yEAAyE;AAEzE,wFAAwF"}
package/lib/ship.js CHANGED
@@ -92,60 +92,6 @@ export const httpShip = async (intake, token, shipment) => {
92
92
  const text = await answer.text();
93
93
  return { ok: answer.ok, status: answer.status, said: saidOf(text), accepted: answer.ok ? acceptedOf(text) : null };
94
94
  };
95
- /** Отбитые строки из ответа приёма. Не список — пусто: своё устройство приём не пересказывает. */
96
- function deniedOf(raw) {
97
- if (!Array.isArray(raw)) {
98
- return [];
99
- }
100
- return raw
101
- .filter((one) => typeof one === 'object' && one !== null)
102
- .map((one) => {
103
- const line = one;
104
- return {
105
- at: typeof line['at'] === 'number' ? line['at'] : 0,
106
- kind: String(line['kind']),
107
- key: String(line['key']),
108
- denial: String(line['denial']),
109
- };
110
- });
111
- }
112
95
  /** Два числа и отбитые строки из ответа приёма. Ответ не тем — пусто. */
113
- function stateAcceptedOf(text) {
114
- try {
115
- const said = JSON.parse(text);
116
- return typeof said['tree'] === 'string' && typeof said['changed'] === 'number'
117
- ? {
118
- tree: said['tree'],
119
- changed: said['changed'],
120
- same: typeof said['same'] === 'number' ? said['same'] : 0,
121
- denied: deniedOf(said['denied']),
122
- }
123
- : null;
124
- }
125
- catch {
126
- return null;
127
- }
128
- }
129
96
  /** Запрос правки состояния. Токен уезжает заголовком и в теле не появляется ни разу. */
130
- export const httpMark = async (intake, token, body) => {
131
- let answer;
132
- try {
133
- answer = await fetch(intakeUrl(intake, 'states'), {
134
- method: 'POST',
135
- headers: { 'content-type': 'application/json', [TREE_TOKEN_HEADER]: token },
136
- body: JSON.stringify(body),
137
- signal: AbortSignal.timeout(SHIP_TIMEOUT_MS),
138
- });
139
- }
140
- catch (error) {
141
- return { ok: false, status: 0, said: error.message, accepted: null };
142
- }
143
- const text = await answer.text();
144
- return {
145
- ok: answer.ok,
146
- status: answer.status,
147
- said: saidOf(text),
148
- accepted: answer.ok ? stateAcceptedOf(text) : null,
149
- };
150
- };
151
97
  //# sourceMappingURL=ship.js.map