@rt-tools/agent-kit 0.16.1 → 0.18.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 (136) hide show
  1. package/assets/checks/board-epics.github.mjs +123 -0
  2. package/assets/checks/board-gh.github.mjs +121 -0
  3. package/assets/checks/board-paths.github.mjs +88 -0
  4. package/assets/checks/board-runs.github.mjs +8 -0
  5. package/assets/checks/board-titles.github.mjs +66 -0
  6. package/assets/checks/board.github.mjs +77 -73
  7. package/assets/checks/check-board.github.mjs +56 -7
  8. package/assets/checks/check-file-size.mjs +10 -2
  9. package/assets/checks/check-glossary.mjs +170 -0
  10. package/assets/checks/check-hook-scope.mjs +126 -0
  11. package/assets/checks/check-profile-drift.mjs +195 -0
  12. package/assets/checks/check-push-gate.mjs +59 -1
  13. package/assets/checks/check-schema-drift.mjs +12 -5
  14. package/assets/checks/check-specs.mjs +1 -1
  15. package/assets/checks/rt-kit-checks.config.mjs +13 -0
  16. package/assets/checks/spec-anchors.mjs +2 -2
  17. package/assets/checks/spec-contract.mjs +9 -0
  18. package/assets/defaults/gate-map.sh +13 -4
  19. package/assets/defaults/project.sh +31 -108
  20. package/assets/defaults/shell.sh +139 -0
  21. package/assets/docs/GLOSSARY.md +1 -1
  22. package/assets/hooks/browser-device-id.sh +20 -4
  23. package/assets/hooks/browser-guard-device-id.sh +5 -2
  24. package/assets/hooks/browser-guard-no-other-drivers.sh +41 -2
  25. package/assets/hooks/claim-guard.sh +22 -1
  26. package/assets/hooks/docs-guard.sh +1 -1
  27. package/assets/hooks/exam-guard.sh +85 -12
  28. package/assets/hooks/git-guard-delivery-conflict.sh +85 -0
  29. package/assets/hooks/git-guard-delivery-folder.sh +19 -0
  30. package/assets/hooks/git-guard-delivery-signature.sh +14 -5
  31. package/assets/hooks/git-guard-delivery.sh +54 -2
  32. package/assets/hooks/git-guard-push-tests.sh +21 -1
  33. package/assets/hooks/grill-gate-ask.sh +25 -0
  34. package/assets/hooks/grill-gate.sh +75 -11
  35. package/assets/hooks/lint-after-edit.sh +44 -16
  36. package/assets/hooks/proposal-guard.sh +11 -4
  37. package/assets/hooks/rule-source-guard.sh +8 -13
  38. package/assets/hooks/skill-gate-layers.sh +7 -0
  39. package/assets/hooks/skill-gate.sh +29 -0
  40. package/assets/hooks/task-context-load.sh +32 -0
  41. package/assets/hooks/task-flow-context.sh +194 -0
  42. package/assets/hooks/task-flow-draft-guard.sh +109 -0
  43. package/assets/hooks/task-flow-guard.sh +27 -159
  44. package/assets/hooks/turn-exit-guard.sh +53 -88
  45. package/assets/hooks/waiting-turn-guard.sh +65 -2
  46. package/assets/hooks/window-fill-guard.sh +5 -1
  47. package/assets/hooks/work-start-guard.sh +166 -0
  48. package/assets/hooks/write-targets.sh +24 -0
  49. package/assets/laws/delivery.md +19 -0
  50. package/assets/laws/frontend-application.md +4 -0
  51. package/assets/laws/verifiability.md +44 -0
  52. package/assets/laws/work-conduct.md +15 -0
  53. package/assets/patterns/browser-verification-measure.md +3 -1
  54. package/assets/patterns/browser-verification-stand.md +17 -4
  55. package/assets/patterns/doc-style-write.md +62 -2
  56. package/assets/patterns/git-workflow-commit.github.md +33 -5
  57. package/assets/patterns/git-workflow-docker.md +22 -0
  58. package/assets/patterns/git-workflow-freshness.md +87 -0
  59. package/assets/patterns/git-workflow-merge.md +41 -0
  60. package/assets/patterns/git-workflow-migration.md +11 -0
  61. package/assets/patterns/git-workflow-pr.github.md +12 -1
  62. package/assets/patterns/git-workflow-restart.md +18 -0
  63. package/assets/patterns/git-workflow-secrets.md +14 -0
  64. package/assets/patterns/git-workflow-stack.md +93 -1
  65. package/assets/patterns/lib-layers-move.md +4 -0
  66. package/assets/patterns/spec-driven-domain.md +47 -2
  67. package/assets/patterns/spec-driven-rule.md +16 -4
  68. package/assets/patterns/spec-driven-sweep.md +57 -0
  69. package/assets/patterns/status-report-table.github.md +1 -1
  70. package/assets/patterns/task-flow-archive.md +58 -16
  71. package/assets/patterns/task-flow-close.md +106 -23
  72. package/assets/patterns/task-flow-handoff.md +14 -2
  73. package/assets/patterns/task-flow-resume.md +35 -8
  74. package/assets/patterns/task-flow-start.md +60 -22
  75. package/assets/patterns/testing-e2e.md +23 -0
  76. package/assets/patterns/turn-entry-map.md +1 -1
  77. package/assets/pitfalls/agent-kit.md +71 -3
  78. package/assets/pitfalls/doc-style.md +15 -0
  79. package/assets/pitfalls/git-workflow.github.md +88 -0
  80. package/assets/pitfalls/spec-driven.md +22 -0
  81. package/assets/pitfalls/task-flow.md +113 -0
  82. package/assets/pitfalls/testing.md +6 -0
  83. package/assets/pitfalls/turn-conduct.md +49 -0
  84. package/assets/rules/browser-verification.md +58 -0
  85. package/assets/rules/deploy-flow.azure.md +8 -0
  86. package/assets/rules/deploy-flow.github.md +27 -0
  87. package/assets/rules/deploy-flow.gitlab.md +8 -0
  88. package/assets/rules/doc-style.md +20 -0
  89. package/assets/rules/git-workflow.azure.md +22 -2
  90. package/assets/rules/git-workflow.github.md +127 -118
  91. package/assets/rules/git-workflow.gitlab.md +17 -4
  92. package/assets/rules/lists.md +5 -0
  93. package/assets/rules/observability.needs-app.md +4 -0
  94. package/assets/rules/reuse-first.md +14 -0
  95. package/assets/rules/shared-code.md +5 -0
  96. package/assets/rules/spec-driven.md +33 -15
  97. package/assets/rules/task-flow.md +132 -126
  98. package/assets/rules/testing.md +41 -2
  99. package/assets/rules/turn-conduct.md +73 -57
  100. package/assets/rules/turn-entry.md +6 -0
  101. package/assets/samples/tasks/_template/grill.md +5 -0
  102. package/assets/samples/tasks/_template/plan.md +3 -0
  103. package/assets/skills/agent-kit.md +108 -76
  104. package/assets/templates/postmortem.md +5 -1
  105. package/bin/agent-kit.d.ts.map +1 -1
  106. package/bin/agent-kit.js +30 -6
  107. package/bin/agent-kit.js.map +1 -1
  108. package/lib/catalog.d.ts.map +1 -1
  109. package/lib/catalog.js +2 -1
  110. package/lib/catalog.js.map +1 -1
  111. package/lib/commands.d.ts.map +1 -1
  112. package/lib/commands.js +96 -7
  113. package/lib/commands.js.map +1 -1
  114. package/lib/hooks-map.d.ts +26 -0
  115. package/lib/hooks-map.d.ts.map +1 -1
  116. package/lib/hooks-map.js +58 -2
  117. package/lib/hooks-map.js.map +1 -1
  118. package/lib/sections.d.ts +6 -0
  119. package/lib/sections.d.ts.map +1 -1
  120. package/lib/sections.js +19 -0
  121. package/lib/sections.js.map +1 -1
  122. package/lib/shipment.d.ts +2 -0
  123. package/lib/shipment.d.ts.map +1 -1
  124. package/lib/shipment.fixture.d.ts +5 -0
  125. package/lib/shipment.fixture.d.ts.map +1 -1
  126. package/lib/shipment.fixture.js +7 -0
  127. package/lib/shipment.fixture.js.map +1 -1
  128. package/lib/shipment.js +13 -1
  129. package/lib/shipment.js.map +1 -1
  130. package/lib/sync.d.ts +35 -3
  131. package/lib/sync.d.ts.map +1 -1
  132. package/lib/sync.js +59 -8
  133. package/lib/sync.js.map +1 -1
  134. package/package.json +1 -1
  135. package/rt-tools-agent-kit-0.18.0.tgz +0 -0
  136. package/rt-tools-agent-kit-0.16.1.tgz +0 -0
@@ -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 */\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"]}
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, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, 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\n/** Отступ настройки: тот, которым она написана. Угаданный переписал бы файл целиком. */\nfunction indentOf(text: string): number {\n const found: RegExpExecArray | null = /^([ \\t]+)\"/m.exec(text);\n\n return found === null ? 2 : found[1].length;\n}\n\n/** Настройка целиком либо `null`, если её не разобрать. Комментарии дерева JSON не читает. */\nfunction readSettings(text: string): Record<string, unknown> | null {\n try {\n const parsed: unknown = JSON.parse(text);\n\n return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};\n } catch {\n return null;\n }\n}\n\n/** Что запись объявления сделала с настройкой дерева. */\nexport interface IBindResult {\n /** События, записи на которые дописаны. Пусто — настройка уже звала диспетчер на всё. */\n readonly added: readonly string[];\n /**\n * Осталась ли настройка нетронутой не потому, что дописывать было нечего. Настройку, которую\n * не разобрать, пакет не правит: дерево вправе держать её с комментариями, а переписанная\n * своим разбором она их теряет молча.\n */\n readonly unreadable: boolean;\n}\n\n/**\n * Запись объявления в настройку агента: событие, которого там нет, встаёт записью к диспетчеру.\n *\n * Прежде раскладка клала файл гарда и печатала готовый кусок, прося вставить его рукой. Свежая\n * установка уезжала с полным набором разложенных гардов, которых не звал никто, и узнавала об\n * этом на первом промахе, прошедшем мимо. Довод, по которому пакет в чужую настройку не писал,\n * назывался прямо: слияние чужого JSON молча теряет несовпавшее. Записан он был тогда, когда\n * куском была карта из двух десятков гардов по образцу вызова; с приходом диспетчера кусок стал\n * строкой на событие, и терять стало нечего — запись только прибавляется.\n *\n * ЧТО ЗДЕСЬ НИКОГДА НЕ ДЕЛАЕТСЯ: ни одна стоящая запись не переписывается и не снимается — ни\n * пакетная, ни чужая. Дерево, снявшее гард своим решением, правкой не наказывается: раскладка о\n * снятом говорит, а не возвращает его.\n */\nexport function bindDispatch(bindings: readonly IHookBinding[], root: string): IBindResult {\n const path: string = join(root, SETTINGS_PATH);\n const text: string = existsSync(path) ? readFileSync(path, 'utf8') : '{}\\n';\n const settings: Record<string, unknown> | null = readSettings(text);\n\n if (settings === null) {\n return { added: [], unreadable: true };\n }\n\n const events: readonly string[] = [...new Set(bindings.map((binding: IHookBinding): string => binding.event))]\n .filter((event: string): boolean => !dispatchesEvent(text, event))\n .sort((left: string, right: string): number => left.localeCompare(right));\n\n if (!events.length) {\n return { added: [], unreadable: false };\n }\n\n const hooks: Record<string, unknown> = { ...((settings['hooks'] ?? {}) as Record<string, unknown>) };\n for (const event of events) {\n const records: unknown = hooks[event];\n // Запись дописывается в конец, а не встаёт вместо чужой: у события бывают и свои гарды\n // дерева, и порядок среди них — решение дерева.\n hooks[event] = [\n ...(Array.isArray(records) ? records : []),\n { hooks: [{ type: 'command', command: `$CLAUDE_PROJECT_DIR/${DISPATCH_PATH} ${event}` }] },\n ];\n }\n\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify({ ...settings, hooks }, null, indentOf(text)) + '\\n', 'utf8');\n\n return { added: events, unreadable: false };\n}\n"]}
package/lib/sections.d.ts CHANGED
@@ -21,6 +21,12 @@ export interface IDocument {
21
21
  readonly preamble: string;
22
22
  readonly sections: readonly ISection[];
23
23
  }
24
+ /**
25
+ * Строки текста вне ограды блоков кода. Строка примера — часть образца, а не утверждение
26
+ * документа о себе: прочитанная наравне с остальными, она заставляет ресурс требовать то, чего
27
+ * он показывает написание.
28
+ */
29
+ export declare function linesOutsideFences(text: string): readonly string[];
24
30
  export declare function parseDocument(text: string): IDocument;
25
31
  export declare function renderDocument(document: IDocument): string;
26
32
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sections.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,uFAAuF;AACvF,MAAM,WAAW,QAAQ;IACrB,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAC;CAC1C;AASD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAgCrD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,GAAG,MAAM,CAO1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAgCrF"}
1
+ {"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sections.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,uFAAuF;AACvF,MAAM,WAAW,QAAQ;IACrB,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAC;CAC1C;AAMD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAelE;AAKD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAgCrD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,GAAG,MAAM,CAO1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAgCrF"}
package/lib/sections.js CHANGED
@@ -12,6 +12,25 @@
12
12
  const SECTION_HEADING = /^##\s+\S/;
13
13
  /** Ограда блока кода: заголовок внутри неё — часть примера, а не раздел документа. */
14
14
  const FENCE = /^\s*(```|~~~)/;
15
+ /**
16
+ * Строки текста вне ограды блоков кода. Строка примера — часть образца, а не утверждение
17
+ * документа о себе: прочитанная наравне с остальными, она заставляет ресурс требовать то, чего
18
+ * он показывает написание.
19
+ */
20
+ export function linesOutsideFences(text) {
21
+ const kept = [];
22
+ let fenced = false;
23
+ for (const line of text.split('\n')) {
24
+ if (FENCE.test(line)) {
25
+ fenced = !fenced;
26
+ continue;
27
+ }
28
+ if (!fenced) {
29
+ kept.push(line);
30
+ }
31
+ }
32
+ return kept;
33
+ }
15
34
  const trimEdges = (lines) => lines.join('\n').replace(/^\n+/, '').trimEnd();
16
35
  export function parseDocument(text) {
17
36
  const preamble = [];
@@ -1 +1 @@
1
- {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sections.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Разбор документа на разделы `## ` и слияние надстройки проекта с текстом пакета.\n *\n * Пакет везёт общий текст, проект дописывает своё. Заменять файл целиком нельзя — тогда правка\n * пакета до проекта не доедет и этого никто не заметит; не заменять ничего тоже нельзя — общий\n * текст всегда чем-то расходится с местом, куда его положили.\n *\n * Отсюда единица слияния — раздел `## `: он крупнее строки, поэтому слияние не рвёт фразу\n * посередине, и мельче файла, поэтому правка пакета доезжает во все разделы, которых проект не\n * трогал.\n */\n\n/** Раздел документа: заголовок `## …` и всё до следующего заголовка того же уровня. */\nexport interface ISection {\n /** Строка заголовка целиком, вместе с решётками. */\n readonly heading: string;\n /** Текст под заголовком без краевых пустых строк. */\n readonly body: string;\n}\n\n/** Документ: вступление до первого `## ` и разделы по порядку. */\nexport interface IDocument {\n readonly preamble: string;\n readonly sections: readonly ISection[];\n}\n\nconst SECTION_HEADING: RegExp = /^##\\s+\\S/;\n/** Ограда блока кода: заголовок внутри неё — часть примера, а не раздел документа. */\nconst FENCE: RegExp = /^\\s*(```|~~~)/;\n\nconst trimEdges: (lines: readonly string[]) => string = (lines: readonly string[]): string =>\n lines.join('\\n').replace(/^\\n+/, '').trimEnd();\n\nexport function parseDocument(text: string): IDocument {\n const preamble: string[] = [];\n const sections: ISection[] = [];\n let heading: string | null = null;\n let body: string[] = [];\n let fenced: boolean = false;\n\n const flush: () => void = (): void => {\n if (heading !== null) {\n sections.push({ heading, body: trimEdges(body) });\n }\n };\n\n for (const line of text.split('\\n')) {\n if (FENCE.test(line)) {\n fenced = !fenced;\n }\n if (!fenced && SECTION_HEADING.test(line)) {\n flush();\n heading = line.trimEnd();\n body = [];\n continue;\n }\n if (heading === null) {\n preamble.push(line);\n } else {\n body.push(line);\n }\n }\n flush();\n\n return { preamble: trimEdges(preamble), sections };\n}\n\nexport function renderDocument(document: IDocument): string {\n const parts: string[] = document.preamble ? [document.preamble] : [];\n for (const section of document.sections) {\n parts.push(section.body ? `${section.heading}\\n\\n${section.body}` : section.heading);\n }\n\n return `${parts.join('\\n\\n')}\\n`;\n}\n\n/**\n * Текст пакета с наложенной надстройкой проекта.\n *\n * Три случая, и нужны все три: совпавший заголовок **замещает** раздел пакета, новый —\n * **дописывается** в конец, пустой — **снимает** раздел. Без снятия проект не мог бы отказаться\n * от общего раздела иначе, чем правкой разложенного файла, а такая правка теряется на следующем\n * же `sync`.\n *\n * Вступление надстройки замещает вступление пакета, только если оно не пустое: у надстройки,\n * правящей один раздел, вступления нет вовсе.\n */\nexport function mergeDocuments(base: IDocument, override: IDocument | null): IDocument {\n if (!override) {\n return base;\n }\n\n const byHeading: Map<string, ISection> = new Map();\n for (const section of override.sections) {\n byHeading.set(section.heading, section);\n }\n\n const taken: Set<string> = new Set();\n const sections: ISection[] = [];\n\n for (const section of base.sections) {\n const replacement: ISection | undefined = byHeading.get(section.heading);\n if (!replacement) {\n sections.push(section);\n continue;\n }\n taken.add(section.heading);\n if (replacement.body) {\n sections.push(replacement);\n }\n }\n\n for (const section of override.sections) {\n if (!taken.has(section.heading) && section.body) {\n sections.push(section);\n }\n }\n\n return { preamble: override.preamble || base.preamble, sections };\n}\n"]}
1
+ {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sections.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Разбор документа на разделы `## ` и слияние надстройки проекта с текстом пакета.\n *\n * Пакет везёт общий текст, проект дописывает своё. Заменять файл целиком нельзя — тогда правка\n * пакета до проекта не доедет и этого никто не заметит; не заменять ничего тоже нельзя — общий\n * текст всегда чем-то расходится с местом, куда его положили.\n *\n * Отсюда единица слияния — раздел `## `: он крупнее строки, поэтому слияние не рвёт фразу\n * посередине, и мельче файла, поэтому правка пакета доезжает во все разделы, которых проект не\n * трогал.\n */\n\n/** Раздел документа: заголовок `## …` и всё до следующего заголовка того же уровня. */\nexport interface ISection {\n /** Строка заголовка целиком, вместе с решётками. */\n readonly heading: string;\n /** Текст под заголовком без краевых пустых строк. */\n readonly body: string;\n}\n\n/** Документ: вступление до первого `## ` и разделы по порядку. */\nexport interface IDocument {\n readonly preamble: string;\n readonly sections: readonly ISection[];\n}\n\nconst SECTION_HEADING: RegExp = /^##\\s+\\S/;\n/** Ограда блока кода: заголовок внутри неё — часть примера, а не раздел документа. */\nconst FENCE: RegExp = /^\\s*(```|~~~)/;\n\n/**\n * Строки текста вне ограды блоков кода. Строка примера — часть образца, а не утверждение\n * документа о себе: прочитанная наравне с остальными, она заставляет ресурс требовать то, чего\n * он показывает написание.\n */\nexport function linesOutsideFences(text: string): readonly string[] {\n const kept: string[] = [];\n let fenced: boolean = false;\n\n for (const line of text.split('\\n')) {\n if (FENCE.test(line)) {\n fenced = !fenced;\n continue;\n }\n if (!fenced) {\n kept.push(line);\n }\n }\n\n return kept;\n}\n\nconst trimEdges: (lines: readonly string[]) => string = (lines: readonly string[]): string =>\n lines.join('\\n').replace(/^\\n+/, '').trimEnd();\n\nexport function parseDocument(text: string): IDocument {\n const preamble: string[] = [];\n const sections: ISection[] = [];\n let heading: string | null = null;\n let body: string[] = [];\n let fenced: boolean = false;\n\n const flush: () => void = (): void => {\n if (heading !== null) {\n sections.push({ heading, body: trimEdges(body) });\n }\n };\n\n for (const line of text.split('\\n')) {\n if (FENCE.test(line)) {\n fenced = !fenced;\n }\n if (!fenced && SECTION_HEADING.test(line)) {\n flush();\n heading = line.trimEnd();\n body = [];\n continue;\n }\n if (heading === null) {\n preamble.push(line);\n } else {\n body.push(line);\n }\n }\n flush();\n\n return { preamble: trimEdges(preamble), sections };\n}\n\nexport function renderDocument(document: IDocument): string {\n const parts: string[] = document.preamble ? [document.preamble] : [];\n for (const section of document.sections) {\n parts.push(section.body ? `${section.heading}\\n\\n${section.body}` : section.heading);\n }\n\n return `${parts.join('\\n\\n')}\\n`;\n}\n\n/**\n * Текст пакета с наложенной надстройкой проекта.\n *\n * Три случая, и нужны все три: совпавший заголовок **замещает** раздел пакета, новый —\n * **дописывается** в конец, пустой — **снимает** раздел. Без снятия проект не мог бы отказаться\n * от общего раздела иначе, чем правкой разложенного файла, а такая правка теряется на следующем\n * же `sync`.\n *\n * Вступление надстройки замещает вступление пакета, только если оно не пустое: у надстройки,\n * правящей один раздел, вступления нет вовсе.\n */\nexport function mergeDocuments(base: IDocument, override: IDocument | null): IDocument {\n if (!override) {\n return base;\n }\n\n const byHeading: Map<string, ISection> = new Map();\n for (const section of override.sections) {\n byHeading.set(section.heading, section);\n }\n\n const taken: Set<string> = new Set();\n const sections: ISection[] = [];\n\n for (const section of base.sections) {\n const replacement: ISection | undefined = byHeading.get(section.heading);\n if (!replacement) {\n sections.push(section);\n continue;\n }\n taken.add(section.heading);\n if (replacement.body) {\n sections.push(replacement);\n }\n }\n\n for (const section of override.sections) {\n if (!taken.has(section.heading) && section.body) {\n sections.push(section);\n }\n }\n\n return { preamble: override.preamble || base.preamble, sections };\n}\n"]}
package/lib/shipment.d.ts CHANGED
@@ -10,6 +10,8 @@ export interface IShipOptions {
10
10
  readonly ship: TShip;
11
11
  /** Адрес удалённого репозитория этого дерева: из него считается признак. */
12
12
  readonly remote: string;
13
+ /** Имена удалённых репозиториев. Нужны отказу: выбор между несколькими делает человек. */
14
+ readonly remotes?: readonly string[];
13
15
  /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */
14
16
  readonly today: string;
15
17
  readonly days: number;
@@ -1 +1 @@
1
- {"version":3,"file":"shipment.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"AAcA,OAAO,EAGH,cAAc,EACd,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,aAAa,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAe,QAAQ,EAA+B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAS,SAAS,EAAyE,MAAM,gBAAgB,CAAC;AACzH,OAAO,EAAE,SAAS,EAAuB,KAAK,EAAE,MAAM,WAAW,CAAC;AAalE,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjE;AAaD,uFAAuF;AACvF,wBAAgB,YAAY,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,aAAa,CAkBf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAe5E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,GAAG,SAAS,SAAS,EAAE,CAMpI;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWxH;AAkID;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgHlG"}
1
+ {"version":3,"file":"shipment.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"AAcA,OAAO,EAGH,cAAc,EACd,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,aAAa,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAe,QAAQ,EAA+B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAS,SAAS,EAAyE,MAAM,gBAAgB,CAAC;AACzH,OAAO,EAAE,SAAS,EAAuB,KAAK,EAAE,MAAM,WAAW,CAAC;AAalE,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjE;AAaD,uFAAuF;AACvF,wBAAgB,YAAY,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,aAAa,CAkBf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAe5E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,GAAG,SAAS,SAAS,EAAE,CAMpI;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWxH;AAsJD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA6GlG"}
@@ -26,6 +26,11 @@ export declare function refusing(status: number, message: string): TShip;
26
26
  /** Настройка дерева: конфиг заводится командой, а ключи отправки дописываются поверх. */
27
27
  export declare function start(patch?: Record<string, unknown>): void;
28
28
  export declare function shipping(ship?: TShip, dryRun?: boolean): Promise<IOutcomeOfCommand>;
29
+ /**
30
+ * Отправка дерева, у которого адреса нет. Имена удалённых репозиториев называются отдельно:
31
+ * между «репозитория нет вовсе» и «их несколько, а выбрать некому» отказ говорит разное.
32
+ */
33
+ export declare function shippingWithout(remotes?: readonly string[]): Promise<IOutcomeOfCommand>;
29
34
  /** Блоки предложений ложатся одним файлом дня: их порядок и есть порядок в файле. */
30
35
  export declare function proposals(blocks: readonly string[]): void;
31
36
  /** Забыть уехавшее: спеке о повторной отправке нужен чистый счёт со второго прогона. */
@@ -1 +1 @@
1
- {"version":3,"file":"shipment.fixture.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.fixture.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAY,KAAK,EAAE,MAAM,WAAW,CAAC;AAGvD,eAAO,MAAM,OAAO,EAAE,MAAgB,CAAC;AACvC,eAAO,MAAM,KAAK,EAAE,MAAqB,CAAC;AAC1C,wFAAwF;AACxF,eAAO,MAAM,MAAM,EAAE,MAA8C,CAAC;AACpE,eAAO,MAAM,MAAM,EAAE,MAA8B,CAAC;AACpD,eAAO,MAAM,MAAM,EAAE,MAAyC,CAAC;AAC/D,eAAO,MAAM,UAAU,EAAE,MAAwB,CAAC;AAClD,eAAO,MAAM,KAAK,EAAE,MAA6B,CAAC;AAClD,gEAAgE;AAChE,eAAO,MAAM,aAAa,EAAE,MAAY,CAAC;AACzC,+FAA+F;AAC/F,eAAO,MAAM,cAAc,EAAE,MAA+C,CAAC;AAE7E,mFAAmF;AACnF,eAAO,IAAI,IAAI,EAAE,SAAS,EAAO,CAAC;AAKlC,kGAAkG;AAClG,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAGpD;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAEvD;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,OAAO,GAAE,OAAc,GAAG,KAAK,CAWxD;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAM/D;AAED,yFAAyF;AACzF,wBAAgB,KAAK,CAAC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI,CAK/D;AAED,wBAAgB,QAAQ,CAAC,IAAI,GAAE,KAAmB,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAEvG;AAED,qFAAqF;AACrF,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAEzD;AAED,wFAAwF;AACxF,wBAAgB,SAAS,IAAI,IAAI,CAEhC;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,IAAI,SAAS,MAAM,EAAE,CAElD;AAED,gEAAgE;AAChE,wBAAgB,SAAS,IAAI,IAAI,CAIhC;AAED,+CAA+C;AAC/C,wBAAgB,QAAQ,IAAI,IAAI,CAE/B"}
1
+ {"version":3,"file":"shipment.fixture.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.fixture.ts"],"names":[],"mappings":"AAcA,OAAO,EAAsB,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAY,KAAK,EAAE,MAAM,WAAW,CAAC;AAGvD,eAAO,MAAM,OAAO,EAAE,MAAgB,CAAC;AACvC,eAAO,MAAM,KAAK,EAAE,MAAqB,CAAC;AAC1C,wFAAwF;AACxF,eAAO,MAAM,MAAM,EAAE,MAA8C,CAAC;AACpE,eAAO,MAAM,MAAM,EAAE,MAA8B,CAAC;AACpD,eAAO,MAAM,MAAM,EAAE,MAAyC,CAAC;AAC/D,eAAO,MAAM,UAAU,EAAE,MAAwB,CAAC;AAClD,eAAO,MAAM,KAAK,EAAE,MAA6B,CAAC;AAClD,gEAAgE;AAChE,eAAO,MAAM,aAAa,EAAE,MAAY,CAAC;AACzC,+FAA+F;AAC/F,eAAO,MAAM,cAAc,EAAE,MAA+C,CAAC;AAE7E,mFAAmF;AACnF,eAAO,IAAI,IAAI,EAAE,SAAS,EAAO,CAAC;AAKlC,kGAAkG;AAClG,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAGpD;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExC;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAEvD;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,OAAO,GAAE,OAAc,GAAG,KAAK,CAWxD;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAM/D;AAED,yFAAyF;AACzF,wBAAgB,KAAK,CAAC,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI,CAK/D;AAED,wBAAgB,QAAQ,CAAC,IAAI,GAAE,KAAmB,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAEvG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,SAAS,MAAM,EAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAE3F;AAED,qFAAqF;AACrF,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAEzD;AAED,wFAAwF;AACxF,wBAAgB,SAAS,IAAI,IAAI,CAEhC;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,IAAI,SAAS,MAAM,EAAE,CAElD;AAED,gEAAgE;AAChE,wBAAgB,SAAS,IAAI,IAAI,CAIhC;AAED,+CAA+C;AAC/C,wBAAgB,QAAQ,IAAI,IAAI,CAE/B"}
@@ -74,6 +74,13 @@ export function start(patch = {}) {
74
74
  export function shipping(ship = accepting(), dryRun = false) {
75
75
  return propose(env, { dryRun, ship, remote: REMOTE, today: TODAY, days: 3 });
76
76
  }
77
+ /**
78
+ * Отправка дерева, у которого адреса нет. Имена удалённых репозиториев называются отдельно:
79
+ * между «репозитория нет вовсе» и «их несколько, а выбрать некому» отказ говорит разное.
80
+ */
81
+ export function shippingWithout(remotes = []) {
82
+ return propose(env, { remotes, dryRun: false, ship: accepting(), remote: '', today: TODAY, days: 3 });
83
+ }
77
84
  /** Блоки предложений ложатся одним файлом дня: их порядок и есть порядок в файле. */
78
85
  export function proposals(blocks) {
79
86
  put(PROPOSALS_FILE, `# Предложения\n\n${blocks.join('\n\n')}\n`);
@@ -1 +1 @@
1
- {"version":3,"file":"shipment.fixture.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.fixture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Одноразовое дерево для спек отправки: настройка, двойники приёма и блоки предложений.\n *\n * Живёт отдельным модулем потому, что спек об отправке две — про сам груз и про отбой блоков, —\n * и обвязка у них одна. Скопированная во второй файл, она разошлась бы с первым молча: двойник\n * приёма отвечает полем, которое читают обе.\n *\n * Хуки жизни дерева модуль не ставит: он не набор спек, и глобальных хуков среды прогона у него\n * нет. Заводит и сносит дерево вызывающий — двумя строками в своих хуках.\n */\nimport { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { IEnvironment, init, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH } from './config.js';\nimport { PROPOSALS_DIR } from './proposals.js';\nimport { IShipment, IShipped, TShip } from './ship.js';\nimport { propose } from './shipment.js';\n\nexport const VERSION: string = '0.1.0';\nexport const TODAY: string = '2026-08-14';\n/** Ресурсы берутся из дерева пакета: снимок надстроек снимается с настоящего текста. */\nexport const ASSETS: string = join(__dirname, '..', '..', 'assets');\nexport const INTAKE: string = 'https://intake.test';\nexport const REMOTE: string = 'git@github.test:owner/tree.git';\nexport const TOKEN_FILE: string = '.secret-token';\nexport const TOKEN: string = 'токен-этого-дерева';\n/** Ответ приёма о непринятом токене: им отвечает отозванный. */\nexport const TOKEN_REFUSED: number = 401;\n/** Файл предложений одноразового дерева: имя одно на все спеки — по нему же читают отметки. */\nexport const PROPOSALS_FILE: string = `${PROPOSALS_DIR}/2026-08-12-probe.md`;\n\n/** Что уехало двойником — по запросу на строку, в том порядке, в каком уезжало. */\nexport let sent: IShipment[] = [];\n\nlet root: string = '';\nlet env: IEnvironment = { root: '', version: VERSION, assetsDir: ASSETS };\n\n/** Корень одноразового дерева: он меняется каждой спекой, поэтому спрашивается, а не хранится. */\nexport function treeRoot(): string {\n return root;\n}\n\nexport function put(path: string, text: string): void {\n mkdirSync(dirname(join(root, path)), { recursive: true });\n writeFileSync(join(root, path), text, 'utf8');\n}\n\nexport function get(path: string): string {\n return readFileSync(join(root, path), 'utf8');\n}\n\nexport function said(outcome: IOutcomeOfCommand): string {\n return outcome.lines.join('\\n');\n}\n\n/** Двойник приёма: отвечает принятым и называет месяц, как настоящий. */\nexport function accepting(created: boolean = true): TShip {\n return async (intake: string, token: string, shipment: IShipment): Promise<IShipped> => {\n sent.push(shipment);\n\n return {\n ok: Boolean(intake && token),\n status: created ? 201 : 200,\n said: '',\n accepted: { tree: 'дерево', month: '2026-08', created },\n };\n };\n}\n\n/** Двойник приёма, который груз не принял: отозванный токен отвечает именно так. */\nexport function refusing(status: number, message: string): TShip {\n return async (_intake: string, _token: string, shipment: IShipment): Promise<IShipped> => {\n sent.push(shipment);\n\n return { status, ok: false, said: message, accepted: null };\n };\n}\n\n/** Настройка дерева: конфиг заводится командой, а ключи отправки дописываются поверх. */\nexport function start(patch: Record<string, unknown> = {}): void {\n init(root, [], { host: 'github' });\n const config: Record<string, unknown> = JSON.parse(get(CONFIG_PATH)) as Record<string, unknown>;\n writeFileSync(join(root, CONFIG_PATH), JSON.stringify({ ...config, intake: INTAKE, token: TOKEN_FILE, ...patch }, null, 4), 'utf8');\n put(TOKEN_FILE, `${TOKEN}\\n`);\n}\n\nexport function shipping(ship: TShip = accepting(), dryRun: boolean = false): Promise<IOutcomeOfCommand> {\n return propose(env, { dryRun, ship, remote: REMOTE, today: TODAY, days: 3 });\n}\n\n/** Блоки предложений ложатся одним файлом дня: их порядок и есть порядок в файле. */\nexport function proposals(blocks: readonly string[]): void {\n put(PROPOSALS_FILE, `# Предложения\\n\\n${blocks.join('\\n\\n')}\\n`);\n}\n\n/** Забыть уехавшее: спеке о повторной отправке нужен чистый счёт со второго прогона. */\nexport function clearSent(): void {\n sent = [];\n}\n\n/** Что уехало операциями, по порядку: этим спеки и судят состав груза. */\nexport function operationsSent(): readonly string[] {\n return sent.map((shipment: IShipment): string => shipment.operation);\n}\n\n/** Завести одноразовое дерево: зовётся в `beforeEach` спеки. */\nexport function freshTree(): void {\n root = mkdtempSync(join(tmpdir(), 'agent-kit-ship-'));\n env = { root, version: VERSION, assetsDir: ASSETS };\n sent = [];\n}\n\n/** Снести его: зовётся в `afterEach` спеки. */\nexport function dropTree(): void {\n rmSync(root, { recursive: true, force: true });\n}\n"]}
1
+ {"version":3,"file":"shipment.fixture.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.fixture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Одноразовое дерево для спек отправки: настройка, двойники приёма и блоки предложений.\n *\n * Живёт отдельным модулем потому, что спек об отправке две — про сам груз и про отбой блоков, —\n * и обвязка у них одна. Скопированная во второй файл, она разошлась бы с первым молча: двойник\n * приёма отвечает полем, которое читают обе.\n *\n * Хуки жизни дерева модуль не ставит: он не набор спек, и глобальных хуков среды прогона у него\n * нет. Заводит и сносит дерево вызывающий — двумя строками в своих хуках.\n */\nimport { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { IEnvironment, init, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH } from './config.js';\nimport { PROPOSALS_DIR } from './proposals.js';\nimport { IShipment, IShipped, TShip } from './ship.js';\nimport { propose } from './shipment.js';\n\nexport const VERSION: string = '0.1.0';\nexport const TODAY: string = '2026-08-14';\n/** Ресурсы берутся из дерева пакета: снимок надстроек снимается с настоящего текста. */\nexport const ASSETS: string = join(__dirname, '..', '..', 'assets');\nexport const INTAKE: string = 'https://intake.test';\nexport const REMOTE: string = 'git@github.test:owner/tree.git';\nexport const TOKEN_FILE: string = '.secret-token';\nexport const TOKEN: string = 'токен-этого-дерева';\n/** Ответ приёма о непринятом токене: им отвечает отозванный. */\nexport const TOKEN_REFUSED: number = 401;\n/** Файл предложений одноразового дерева: имя одно на все спеки — по нему же читают отметки. */\nexport const PROPOSALS_FILE: string = `${PROPOSALS_DIR}/2026-08-12-probe.md`;\n\n/** Что уехало двойником — по запросу на строку, в том порядке, в каком уезжало. */\nexport let sent: IShipment[] = [];\n\nlet root: string = '';\nlet env: IEnvironment = { root: '', version: VERSION, assetsDir: ASSETS };\n\n/** Корень одноразового дерева: он меняется каждой спекой, поэтому спрашивается, а не хранится. */\nexport function treeRoot(): string {\n return root;\n}\n\nexport function put(path: string, text: string): void {\n mkdirSync(dirname(join(root, path)), { recursive: true });\n writeFileSync(join(root, path), text, 'utf8');\n}\n\nexport function get(path: string): string {\n return readFileSync(join(root, path), 'utf8');\n}\n\nexport function said(outcome: IOutcomeOfCommand): string {\n return outcome.lines.join('\\n');\n}\n\n/** Двойник приёма: отвечает принятым и называет месяц, как настоящий. */\nexport function accepting(created: boolean = true): TShip {\n return async (intake: string, token: string, shipment: IShipment): Promise<IShipped> => {\n sent.push(shipment);\n\n return {\n ok: Boolean(intake && token),\n status: created ? 201 : 200,\n said: '',\n accepted: { tree: 'дерево', month: '2026-08', created },\n };\n };\n}\n\n/** Двойник приёма, который груз не принял: отозванный токен отвечает именно так. */\nexport function refusing(status: number, message: string): TShip {\n return async (_intake: string, _token: string, shipment: IShipment): Promise<IShipped> => {\n sent.push(shipment);\n\n return { status, ok: false, said: message, accepted: null };\n };\n}\n\n/** Настройка дерева: конфиг заводится командой, а ключи отправки дописываются поверх. */\nexport function start(patch: Record<string, unknown> = {}): void {\n init(root, [], { host: 'github' });\n const config: Record<string, unknown> = JSON.parse(get(CONFIG_PATH)) as Record<string, unknown>;\n writeFileSync(join(root, CONFIG_PATH), JSON.stringify({ ...config, intake: INTAKE, token: TOKEN_FILE, ...patch }, null, 4), 'utf8');\n put(TOKEN_FILE, `${TOKEN}\\n`);\n}\n\nexport function shipping(ship: TShip = accepting(), dryRun: boolean = false): Promise<IOutcomeOfCommand> {\n return propose(env, { dryRun, ship, remote: REMOTE, today: TODAY, days: 3 });\n}\n\n/**\n * Отправка дерева, у которого адреса нет. Имена удалённых репозиториев называются отдельно:\n * между «репозитория нет вовсе» и «их несколько, а выбрать некому» отказ говорит разное.\n */\nexport function shippingWithout(remotes: readonly string[] = []): Promise<IOutcomeOfCommand> {\n return propose(env, { remotes, dryRun: false, ship: accepting(), remote: '', today: TODAY, days: 3 });\n}\n\n/** Блоки предложений ложатся одним файлом дня: их порядок и есть порядок в файле. */\nexport function proposals(blocks: readonly string[]): void {\n put(PROPOSALS_FILE, `# Предложения\\n\\n${blocks.join('\\n\\n')}\\n`);\n}\n\n/** Забыть уехавшее: спеке о повторной отправке нужен чистый счёт со второго прогона. */\nexport function clearSent(): void {\n sent = [];\n}\n\n/** Что уехало операциями, по порядку: этим спеки и судят состав груза. */\nexport function operationsSent(): readonly string[] {\n return sent.map((shipment: IShipment): string => shipment.operation);\n}\n\n/** Завести одноразовое дерево: зовётся в `beforeEach` спеки. */\nexport function freshTree(): void {\n root = mkdtempSync(join(tmpdir(), 'agent-kit-ship-'));\n env = { root, version: VERSION, assetsDir: ASSETS };\n sent = [];\n}\n\n/** Снести его: зовётся в `afterEach` спеки. */\nexport function dropTree(): void {\n rmSync(root, { recursive: true, force: true });\n}\n"]}
package/lib/shipment.js CHANGED
@@ -184,6 +184,18 @@ function accepted(shipped) {
184
184
  const added = shipped.accepted.added;
185
185
  return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;
186
186
  }
187
+ /**
188
+ * Отказ, когда признак дерева считать не из чего. Случая два, и говорят они разное: репозитория
189
+ * нет вовсе — считать не из чего; репозиториев несколько и `origin` среди них нет — адрес есть, а
190
+ * выбрать его может только человек. Угаданное неверно сливает в сводке приёма два дерева в одно,
191
+ * и увидеть это нечем.
192
+ */
193
+ function noTreeMark(names) {
194
+ if (names.length > 1) {
195
+ return refusal(`признак дерева не считается: удалённых репозиториев несколько, а \`origin\` среди них нет — ${names.join(', ')}`, `переименуй тот, что называет это дерево, в \`origin\` — либо назови признак ключом \`tree\` в ${CONFIG_PATH}`);
196
+ }
197
+ return refusal('признак дерева не считается: удалённого репозитория нет', `назови его ключом \`tree\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`);
198
+ }
187
199
  /**
188
200
  * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе
189
201
  * увозит половину и не оставляет следа, по которому видно, какую именно.
@@ -242,7 +254,7 @@ export async function propose(env, options) {
242
254
  }
243
255
  const tree = treeSlugOf(options.remote, config.tree);
244
256
  if (!tree) {
245
- return refusal('признак дерева не считается: удалённого репозитория нет', `назови его ключом \`tree\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`);
257
+ return noTreeMark(options.remotes ?? []);
246
258
  }
247
259
  const token = readToken(root, config.token);
248
260
  if (!token) {
@@ -1 +1 @@
1
- {"version":3,"file":"shipment.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Сборка груза и команда отправки.\n *\n * Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без\n * замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево\n * переопределило. Отправка, построенная вокруг предложения, эти данные теряла.\n *\n * Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке\n * подменяется двойником.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport {\n CARGO_SCHEMA_VERSION,\n ICargoCount,\n ICargoOverride,\n IPostmortemItem,\n IPostmortemsCargo,\n IProposalItem,\n IProposalsCargo,\n ISummaryCargo,\n} from './cargo.js';\nimport { IEnvironment, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH, IConfig, readConfig } from './config.js';\nimport { IReadResult, ISummary, readObservations, summarize } from './observations.js';\nimport { ILeak, IProposal, leaksIn, markSent, marksOf, nearestMissing, readProposals, TO_PACKAGE } from './proposals.js';\nimport { IShipment, IShipped, readToken, TShip } from './ship.js';\nimport { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';\nimport { byText } from './order.js';\n\n/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */\nconst SLUG_LENGTH: number = 12;\n\n/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */\nconst REFUSED: number = 1;\n\n/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */\nconst TOKEN_REFUSED: number = 401;\n\n/** Чем отправка живёт: чем отправлять, чем себя выдаёт дерево и за какой отрезок сводка. */\nexport interface IShipOptions {\n /** Печатать, что уехало бы, и не отправлять ничего. */\n readonly dryRun: boolean;\n readonly ship: TShip;\n /** Адрес удалённого репозитория этого дерева: из него считается признак. */\n readonly remote: string;\n /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */\n readonly today: string;\n readonly days: number;\n}\n\n/**\n * Адрес удалённого репозитория, приведённый к одному виду.\n *\n * Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они\n * ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а\n * непривёденные формы дали бы два.\n */\nexport function remoteMarkOf(remote: string): string {\n const named: string = remote\n .trim()\n .replace(/\\.git$/, '')\n .replace(/^[a-z+]+:\\/\\//i, '')\n .replace(/^[^@/]+@/, '')\n .replace(/:/g, '/');\n let end: number = named.length;\n\n while (end > 0 && named[end - 1] === '/') {\n end -= 1;\n }\n\n return named.slice(0, end).toLowerCase();\n}\n\n/**\n * Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.\n *\n * Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих\n * копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе\n * все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.\n */\nexport function treeSlugOf(remote: string, spoken: string): string {\n const mark: string = remoteMarkOf(remote);\n\n return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;\n}\n\n/**\n * Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и\n * наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.\n *\n * Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,\n * а не наблюдения.\n */\nfunction knownCounts(counts: readonly ICargoCount[], known: ReadonlySet<string>): readonly ICargoCount[] {\n return counts.filter((count: ICargoCount): boolean => known.has(count.name));\n}\n\n/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */\nexport function summaryCargo(\n summary: ISummary,\n tree: string,\n overrides: readonly ICargoOverride[],\n unpicked: readonly string[],\n known: ReadonlySet<string>\n): ISummaryCargo {\n return {\n tree,\n unpicked,\n overrides,\n schema: CARGO_SCHEMA_VERSION,\n days: summary.days,\n sessions: summary.sessions,\n loads: knownCounts(summary.loads, known),\n denials: knownCounts(summary.denials, known),\n // Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`\n // или `browser` — слова пакета, одинаковые у всех деревьев.\n kinds: summary.kinds,\n guards: knownCounts(summary.guards, known),\n unused: summary.unused,\n versions: summary.versions,\n total: summary.total,\n };\n}\n\n/**\n * Разборы происшествий, как они лежат на дереве.\n *\n * Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на\n * один вопрос, ради которого его читают.\n */\nexport function readPostmortems(root: string, dir: string): IPostmortemItem[] {\n const path: string = join(root, dir);\n\n if (!existsSync(path)) {\n return [];\n }\n\n return (\n readdirSync(path)\n // Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм\n // промаха, — и в приёме встало бы записью, которой нечего сказать.\n .filter((file: string): boolean => file.endsWith('.md') && file !== 'README.md')\n .sort(byText)\n .map((file: string): IPostmortemItem => ({ file, text: readFileSync(join(path, file), 'utf8') }))\n );\n}\n\n/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */\nexport function shipmentsOf(summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): readonly IShipment[] {\n return [\n { kind: 'сводка', operation: 'summary', body: summary },\n ...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),\n ...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),\n ];\n}\n\n/**\n * Что в грузе называет дерево, из которого он уезжает.\n *\n * Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству\n * называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его\n * отправку.\n */\nexport function leaksOfCargo(summary: ISummaryCargo, proposals: readonly IProposal[], marks: readonly string[]): string[] {\n const inSummary: string[] = leaksIn(JSON.stringify(summary, null, 1), marks).map(\n (leak: ILeak): string => ` сводка: ${leak.why}\\n ${leak.text}`\n );\n const inProposals: string[] = proposals.flatMap((entry: IProposal): string[] =>\n leaksIn(entry.body, marks).map(\n (leak: ILeak): string => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\\n ${leak.text}`\n )\n );\n\n return [...inSummary, ...inProposals];\n}\n\n/** Отказ строками. */\nfunction refusal(...lines: readonly string[]): IOutcomeOfCommand {\n return { code: REFUSED, lines: [...lines] };\n}\n\n/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */\nfunction describe(shipment: IShipment, summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): string {\n if (shipment.operation === 'proposals') {\n return `предложений ${proposals.items.length}`;\n }\n if (shipment.operation === 'postmortems') {\n return `разборов ${postmortems.items.length}`;\n }\n\n return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;\n}\n\n/** Блок, который не уехал, и причина этого. */\ninterface IRefusedProposal {\n readonly proposal: IProposal;\n readonly why: string;\n}\n\n/**\n * Отметка об отбое: отбитый блок остаётся на диске и несёт причину.\n *\n * Удалённый блок пишется следующим заходом заново — следа разбора нет, и повтор возвращается.\n * Отметка держит и то и другое: видно, что разбор был, и видно, почему он не стал правкой.\n */\nfunction markRefused(root: string, refused: readonly IRefusedProposal[]): void {\n for (const one of refused) {\n const path: string = join(root, one.proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), one.proposal, one.why, 'отбито'), 'utf8');\n }\n}\n\n/**\n * Перечень груза: что именно уедет и куда — строками, которые читаются до самой отправки.\n *\n * Печатается он обоими прогонами, и разделены они не окончанием глагола, а первой строкой:\n * «уехало» и «уехало бы» отличаются двумя буквами в хвосте, а строки под ними одинаковы до\n * знака. Прочитанный не тем прогоном, вывод сухого читается сделанной работой — и наоборот,\n * настоящая отправка читается пробой, после которой ничего не делают.\n */\nfunction manifest(\n going: readonly IShipment[],\n summary: ISummaryCargo,\n proposals: IProposalsCargo,\n postmortems: IPostmortemsCargo\n): readonly string[] {\n return going.map((shipment: IShipment): string => ` ${shipment.operation} — ${describe(shipment, summary, proposals, postmortems)}`);\n}\n\n/** Пометка об отправке: по ней предложение второй раз не уезжает. */\nfunction markProposals(root: string, proposals: readonly IProposal[], shipped: IShipped): void {\n const mark: string = `приём:${shipped.accepted?.month ?? 'принято'}`;\n\n for (const proposal of proposals) {\n const path: string = join(root, proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');\n }\n}\n\n/**\n * Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт\n * легшего и уже лежавшего.\n *\n * Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже\n * лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку\n * прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.\n */\nfunction accepted(shipped: IShipped): string {\n if (!shipped.accepted) {\n return 'принято';\n }\n\n const record: string = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;\n const added: number | undefined = shipped.accepted.added;\n\n return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;\n}\n\n/**\n * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе\n * увозит половину и не оставляет следа, по которому видно, какую именно.\n */\nasync function send(\n root: string,\n intake: string,\n tree: string,\n token: string,\n ship: TShip,\n going: readonly IShipment[],\n proposals: readonly IProposal[],\n listed: readonly string[]\n): Promise<IOutcomeOfCommand> {\n // Что уедет, названо до того, как уехало: отказ на втором запросе иначе оставляет человека с\n // одной строкой о нём и без перечня, из которого видно, чего этот отказ стоил.\n const head: readonly string[] = [`ОТПРАВКА — груз уходит в ${intake}, дерево ${tree}:`, ...listed];\n const done: string[] = [];\n\n for (const shipment of going) {\n const shipped: IShipped = await ship(intake, token, shipment);\n\n if (!shipped.ok) {\n return {\n code: REFUSED,\n lines: [\n ...head,\n ...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),\n `${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,\n ...(shipped.status === TOKEN_REFUSED\n ? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']\n : []),\n ],\n };\n }\n\n done.push(` ${shipment.kind} → ${accepted(shipped)}`);\n\n if (shipment.operation === 'proposals') {\n markProposals(root, proposals, shipped);\n }\n }\n\n return { code: 0, lines: [...head, 'уехало:', ...done] };\n}\n\n/**\n * Отправка груза в приём.\n *\n * Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит\n * отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева\n * незачем.\n */\nexport async function propose(env: IEnvironment, options: IShipOptions): Promise<IOutcomeOfCommand> {\n const { root, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n\n if (!config) {\n return refusal(`конфига нет: ${CONFIG_PATH} — заводится \\`agent-kit init\\``);\n }\n if (!config.observe) {\n return {\n code: 0,\n lines: [\n 'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',\n 'выключатель гасит и отправку целиком, вместе со снимком надстроек',\n ],\n };\n }\n if (!config.intake) {\n return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \\`intake\\` в ${CONFIG_PATH}`);\n }\n\n const tree: string = treeSlugOf(options.remote, config.tree);\n if (!tree) {\n return refusal(\n 'признак дерева не считается: удалённого репозитория нет',\n `назови его ключом \\`tree\\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`\n );\n }\n\n const token: string = readToken(root, config.token);\n if (!token) {\n return refusal(\n 'токена дерева нет: груз остался неотправленным',\n `файл с ним называется ключом \\`token\\` в ${CONFIG_PATH} и лежит вне дерева`,\n 'выдаётся токен командой приёма — `tree:add` или `tree:token`'\n );\n }\n\n const read: IReadResult = readObservations(root, options.today, options.days);\n const summary: ISummary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);\n const cargo: ISummaryCargo = summaryCargo(\n summary,\n tree,\n treeSnapshot(config, assetsDir, root),\n unpickedOf(config, assetsDir),\n packagedNames(config, assetsDir)\n );\n\n const ready: readonly IProposal[] = readProposals(root).filter(\n (entry: IProposal): boolean => entry.address === TO_PACKAGE && !entry.sent\n );\n // Блок, не назвавший ближайшего утверждения ресурса, не уезжает: разбор, кончившийся ещё\n // одной статьёй о том, о чём статья уже стоит, снаружи неотличим от разбора, кончившегося\n // исправлением. Отбивается он поимённо, а остальные едут: один непрочитанный ресурс не\n // повод задержать чужую работу.\n const refused: readonly IRefusedProposal[] = ready\n .map((entry: IProposal): IRefusedProposal => ({ proposal: entry, why: nearestMissing(entry, assetsDir) }))\n .filter((entry: IRefusedProposal): boolean => Boolean(entry.why));\n const mine: readonly IProposal[] = ready.filter(\n (entry: IProposal): boolean => !refused.some((one: IRefusedProposal): boolean => one.proposal === entry)\n );\n const proposals: IProposalsCargo = {\n tree,\n schema: CARGO_SCHEMA_VERSION,\n items: mine.map((entry: IProposal): IProposalItem => ({ text: entry.body, address: entry.address, resource: entry.resource })),\n };\n const postmortems: IPostmortemsCargo = { tree, schema: CARGO_SCHEMA_VERSION, items: readPostmortems(root, config.postmortems) };\n\n // Проверка на адрес дерева судит все готовые блоки, а не одни уезжающие: отбитый по цитате\n // лежит на диске и уедет, как только его починят, — а найденная в нём утечка отбивает\n // отправку целиком и должна называться сразу.\n const leaked: readonly string[] = leaksOfCargo(cargo, ready, marksOf(root, options.remote));\n if (leaked.length) {\n return refusal(\n `отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`,\n ...leaked,\n 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»'\n );\n }\n\n const going: readonly IShipment[] = shipmentsOf(cargo, proposals, postmortems);\n\n const listed: readonly string[] = manifest(going, cargo, proposals, postmortems);\n // Отбитое называется обоими прогонами: сухой показывает, что уехало бы, — и отбитое к этому\n // относится наравне с уезжающим.\n const refusedLines: readonly string[] = refused.map(\n (one: IRefusedProposal): string => ` отбито ${one.proposal.file}:${one.proposal.line} — ${one.why}`\n );\n\n if (options.dryRun) {\n return {\n code: 0,\n lines: [\n 'СУХОЙ ПРОГОН — наружу не ушло ничего, отметок об отправке не поставлено',\n `уехало бы в ${config.intake}, дерево ${tree}:`,\n ...listed,\n ...refusedLines,\n ...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),\n 'отправляет это тот же вызов без `--dry-run`',\n ],\n };\n }\n\n // Отметка ставится до отправки: отбитое наружу не едет вовсе, и ждать ответа приёма ей\n // незачем.\n markRefused(root, refused);\n\n const outcome: IOutcomeOfCommand = await send(root, config.intake, tree, token, options.ship, going, mine, [\n ...listed,\n ...refusedLines,\n ]);\n\n return outcome;\n}\n"]}
1
+ {"version":3,"file":"shipment.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Сборка груза и команда отправки.\n *\n * Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без\n * замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево\n * переопределило. Отправка, построенная вокруг предложения, эти данные теряла.\n *\n * Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке\n * подменяется двойником.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport {\n CARGO_SCHEMA_VERSION,\n ICargoCount,\n ICargoOverride,\n IPostmortemItem,\n IPostmortemsCargo,\n IProposalItem,\n IProposalsCargo,\n ISummaryCargo,\n} from './cargo.js';\nimport { IEnvironment, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH, IConfig, readConfig } from './config.js';\nimport { IReadResult, ISummary, readObservations, summarize } from './observations.js';\nimport { ILeak, IProposal, leaksIn, markSent, marksOf, nearestMissing, readProposals, TO_PACKAGE } from './proposals.js';\nimport { IShipment, IShipped, readToken, TShip } from './ship.js';\nimport { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';\nimport { byText } from './order.js';\n\n/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */\nconst SLUG_LENGTH: number = 12;\n\n/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */\nconst REFUSED: number = 1;\n\n/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */\nconst TOKEN_REFUSED: number = 401;\n\n/** Чем отправка живёт: чем отправлять, чем себя выдаёт дерево и за какой отрезок сводка. */\nexport interface IShipOptions {\n /** Печатать, что уехало бы, и не отправлять ничего. */\n readonly dryRun: boolean;\n readonly ship: TShip;\n /** Адрес удалённого репозитория этого дерева: из него считается признак. */\n readonly remote: string;\n /** Имена удалённых репозиториев. Нужны отказу: выбор между несколькими делает человек. */\n readonly remotes?: readonly string[];\n /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */\n readonly today: string;\n readonly days: number;\n}\n\n/**\n * Адрес удалённого репозитория, приведённый к одному виду.\n *\n * Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они\n * ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а\n * непривёденные формы дали бы два.\n */\nexport function remoteMarkOf(remote: string): string {\n const named: string = remote\n .trim()\n .replace(/\\.git$/, '')\n .replace(/^[a-z+]+:\\/\\//i, '')\n .replace(/^[^@/]+@/, '')\n .replace(/:/g, '/');\n let end: number = named.length;\n\n while (end > 0 && named[end - 1] === '/') {\n end -= 1;\n }\n\n return named.slice(0, end).toLowerCase();\n}\n\n/**\n * Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.\n *\n * Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих\n * копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе\n * все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.\n */\nexport function treeSlugOf(remote: string, spoken: string): string {\n const mark: string = remoteMarkOf(remote);\n\n return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;\n}\n\n/**\n * Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и\n * наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.\n *\n * Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,\n * а не наблюдения.\n */\nfunction knownCounts(counts: readonly ICargoCount[], known: ReadonlySet<string>): readonly ICargoCount[] {\n return counts.filter((count: ICargoCount): boolean => known.has(count.name));\n}\n\n/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */\nexport function summaryCargo(\n summary: ISummary,\n tree: string,\n overrides: readonly ICargoOverride[],\n unpicked: readonly string[],\n known: ReadonlySet<string>\n): ISummaryCargo {\n return {\n tree,\n unpicked,\n overrides,\n schema: CARGO_SCHEMA_VERSION,\n days: summary.days,\n sessions: summary.sessions,\n loads: knownCounts(summary.loads, known),\n denials: knownCounts(summary.denials, known),\n // Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`\n // или `browser` — слова пакета, одинаковые у всех деревьев.\n kinds: summary.kinds,\n guards: knownCounts(summary.guards, known),\n unused: summary.unused,\n versions: summary.versions,\n total: summary.total,\n };\n}\n\n/**\n * Разборы происшествий, как они лежат на дереве.\n *\n * Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на\n * один вопрос, ради которого его читают.\n */\nexport function readPostmortems(root: string, dir: string): IPostmortemItem[] {\n const path: string = join(root, dir);\n\n if (!existsSync(path)) {\n return [];\n }\n\n return (\n readdirSync(path)\n // Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм\n // промаха, — и в приёме встало бы записью, которой нечего сказать.\n .filter((file: string): boolean => file.endsWith('.md') && file !== 'README.md')\n .sort(byText)\n .map((file: string): IPostmortemItem => ({ file, text: readFileSync(join(path, file), 'utf8') }))\n );\n}\n\n/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */\nexport function shipmentsOf(summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): readonly IShipment[] {\n return [\n { kind: 'сводка', operation: 'summary', body: summary },\n ...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),\n ...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),\n ];\n}\n\n/**\n * Что в грузе называет дерево, из которого он уезжает.\n *\n * Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству\n * называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его\n * отправку.\n */\nexport function leaksOfCargo(summary: ISummaryCargo, proposals: readonly IProposal[], marks: readonly string[]): string[] {\n const inSummary: string[] = leaksIn(JSON.stringify(summary, null, 1), marks).map(\n (leak: ILeak): string => ` сводка: ${leak.why}\\n ${leak.text}`\n );\n const inProposals: string[] = proposals.flatMap((entry: IProposal): string[] =>\n leaksIn(entry.body, marks).map(\n (leak: ILeak): string => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\\n ${leak.text}`\n )\n );\n\n return [...inSummary, ...inProposals];\n}\n\n/** Отказ строками. */\nfunction refusal(...lines: readonly string[]): IOutcomeOfCommand {\n return { code: REFUSED, lines: [...lines] };\n}\n\n/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */\nfunction describe(shipment: IShipment, summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): string {\n if (shipment.operation === 'proposals') {\n return `предложений ${proposals.items.length}`;\n }\n if (shipment.operation === 'postmortems') {\n return `разборов ${postmortems.items.length}`;\n }\n\n return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;\n}\n\n/** Блок, который не уехал, и причина этого. */\ninterface IRefusedProposal {\n readonly proposal: IProposal;\n readonly why: string;\n}\n\n/**\n * Отметка об отбое: отбитый блок остаётся на диске и несёт причину.\n *\n * Удалённый блок пишется следующим заходом заново — следа разбора нет, и повтор возвращается.\n * Отметка держит и то и другое: видно, что разбор был, и видно, почему он не стал правкой.\n */\nfunction markRefused(root: string, refused: readonly IRefusedProposal[]): void {\n for (const one of refused) {\n const path: string = join(root, one.proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), one.proposal, one.why, 'отбито'), 'utf8');\n }\n}\n\n/**\n * Перечень груза: что именно уедет и куда — строками, которые читаются до самой отправки.\n *\n * Печатается он обоими прогонами, и разделены они не окончанием глагола, а первой строкой:\n * «уехало» и «уехало бы» отличаются двумя буквами в хвосте, а строки под ними одинаковы до\n * знака. Прочитанный не тем прогоном, вывод сухого читается сделанной работой — и наоборот,\n * настоящая отправка читается пробой, после которой ничего не делают.\n */\nfunction manifest(\n going: readonly IShipment[],\n summary: ISummaryCargo,\n proposals: IProposalsCargo,\n postmortems: IPostmortemsCargo\n): readonly string[] {\n return going.map((shipment: IShipment): string => ` ${shipment.operation} — ${describe(shipment, summary, proposals, postmortems)}`);\n}\n\n/** Пометка об отправке: по ней предложение второй раз не уезжает. */\nfunction markProposals(root: string, proposals: readonly IProposal[], shipped: IShipped): void {\n const mark: string = `приём:${shipped.accepted?.month ?? 'принято'}`;\n\n for (const proposal of proposals) {\n const path: string = join(root, proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');\n }\n}\n\n/**\n * Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт\n * легшего и уже лежавшего.\n *\n * Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже\n * лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку\n * прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.\n */\nfunction accepted(shipped: IShipped): string {\n if (!shipped.accepted) {\n return 'принято';\n }\n\n const record: string = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;\n const added: number | undefined = shipped.accepted.added;\n\n return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;\n}\n\n/**\n * Отказ, когда признак дерева считать не из чего. Случая два, и говорят они разное: репозитория\n * нет вовсе — считать не из чего; репозиториев несколько и `origin` среди них нет — адрес есть, а\n * выбрать его может только человек. Угаданное неверно сливает в сводке приёма два дерева в одно,\n * и увидеть это нечем.\n */\nfunction noTreeMark(names: readonly string[]): IOutcomeOfCommand {\n if (names.length > 1) {\n return refusal(\n `признак дерева не считается: удалённых репозиториев несколько, а \\`origin\\` среди них нет — ${names.join(', ')}`,\n `переименуй тот, что называет это дерево, в \\`origin\\` — либо назови признак ключом \\`tree\\` в ${CONFIG_PATH}`\n );\n }\n\n return refusal(\n 'признак дерева не считается: удалённого репозитория нет',\n `назови его ключом \\`tree\\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`\n );\n}\n\n/**\n * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе\n * увозит половину и не оставляет следа, по которому видно, какую именно.\n */\nasync function send(\n root: string,\n intake: string,\n tree: string,\n token: string,\n ship: TShip,\n going: readonly IShipment[],\n proposals: readonly IProposal[],\n listed: readonly string[]\n): Promise<IOutcomeOfCommand> {\n // Что уедет, названо до того, как уехало: отказ на втором запросе иначе оставляет человека с\n // одной строкой о нём и без перечня, из которого видно, чего этот отказ стоил.\n const head: readonly string[] = [`ОТПРАВКА — груз уходит в ${intake}, дерево ${tree}:`, ...listed];\n const done: string[] = [];\n\n for (const shipment of going) {\n const shipped: IShipped = await ship(intake, token, shipment);\n\n if (!shipped.ok) {\n return {\n code: REFUSED,\n lines: [\n ...head,\n ...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),\n `${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,\n ...(shipped.status === TOKEN_REFUSED\n ? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']\n : []),\n ],\n };\n }\n\n done.push(` ${shipment.kind} → ${accepted(shipped)}`);\n\n if (shipment.operation === 'proposals') {\n markProposals(root, proposals, shipped);\n }\n }\n\n return { code: 0, lines: [...head, 'уехало:', ...done] };\n}\n\n/**\n * Отправка груза в приём.\n *\n * Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит\n * отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева\n * незачем.\n */\nexport async function propose(env: IEnvironment, options: IShipOptions): Promise<IOutcomeOfCommand> {\n const { root, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n\n if (!config) {\n return refusal(`конфига нет: ${CONFIG_PATH} — заводится \\`agent-kit init\\``);\n }\n if (!config.observe) {\n return {\n code: 0,\n lines: [\n 'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',\n 'выключатель гасит и отправку целиком, вместе со снимком надстроек',\n ],\n };\n }\n if (!config.intake) {\n return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \\`intake\\` в ${CONFIG_PATH}`);\n }\n\n const tree: string = treeSlugOf(options.remote, config.tree);\n if (!tree) {\n return noTreeMark(options.remotes ?? []);\n }\n\n const token: string = readToken(root, config.token);\n if (!token) {\n return refusal(\n 'токена дерева нет: груз остался неотправленным',\n `файл с ним называется ключом \\`token\\` в ${CONFIG_PATH} и лежит вне дерева`,\n 'выдаётся токен командой приёма — `tree:add` или `tree:token`'\n );\n }\n\n const read: IReadResult = readObservations(root, options.today, options.days);\n const summary: ISummary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);\n const cargo: ISummaryCargo = summaryCargo(\n summary,\n tree,\n treeSnapshot(config, assetsDir, root),\n unpickedOf(config, assetsDir),\n packagedNames(config, assetsDir)\n );\n\n const ready: readonly IProposal[] = readProposals(root).filter(\n (entry: IProposal): boolean => entry.address === TO_PACKAGE && !entry.sent\n );\n // Блок, не назвавший ближайшего утверждения ресурса, не уезжает: разбор, кончившийся ещё\n // одной статьёй о том, о чём статья уже стоит, снаружи неотличим от разбора, кончившегося\n // исправлением. Отбивается он поимённо, а остальные едут: один непрочитанный ресурс не\n // повод задержать чужую работу.\n const refused: readonly IRefusedProposal[] = ready\n .map((entry: IProposal): IRefusedProposal => ({ proposal: entry, why: nearestMissing(entry, assetsDir) }))\n .filter((entry: IRefusedProposal): boolean => Boolean(entry.why));\n const mine: readonly IProposal[] = ready.filter(\n (entry: IProposal): boolean => !refused.some((one: IRefusedProposal): boolean => one.proposal === entry)\n );\n const proposals: IProposalsCargo = {\n tree,\n schema: CARGO_SCHEMA_VERSION,\n items: mine.map((entry: IProposal): IProposalItem => ({ text: entry.body, address: entry.address, resource: entry.resource })),\n };\n const postmortems: IPostmortemsCargo = { tree, schema: CARGO_SCHEMA_VERSION, items: readPostmortems(root, config.postmortems) };\n\n // Проверка на адрес дерева судит все готовые блоки, а не одни уезжающие: отбитый по цитате\n // лежит на диске и уедет, как только его починят, — а найденная в нём утечка отбивает\n // отправку целиком и должна называться сразу.\n const leaked: readonly string[] = leaksOfCargo(cargo, ready, marksOf(root, options.remote));\n if (leaked.length) {\n return refusal(\n `отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`,\n ...leaked,\n 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»'\n );\n }\n\n const going: readonly IShipment[] = shipmentsOf(cargo, proposals, postmortems);\n\n const listed: readonly string[] = manifest(going, cargo, proposals, postmortems);\n // Отбитое называется обоими прогонами: сухой показывает, что уехало бы, — и отбитое к этому\n // относится наравне с уезжающим.\n const refusedLines: readonly string[] = refused.map(\n (one: IRefusedProposal): string => ` отбито ${one.proposal.file}:${one.proposal.line} — ${one.why}`\n );\n\n if (options.dryRun) {\n return {\n code: 0,\n lines: [\n 'СУХОЙ ПРОГОН — наружу не ушло ничего, отметок об отправке не поставлено',\n `уехало бы в ${config.intake}, дерево ${tree}:`,\n ...listed,\n ...refusedLines,\n ...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),\n 'отправляет это тот же вызов без `--dry-run`',\n ],\n };\n }\n\n // Отметка ставится до отправки: отбитое наружу не едет вовсе, и ждать ответа приёма ей\n // незачем.\n markRefused(root, refused);\n\n const outcome: IOutcomeOfCommand = await send(root, config.intake, tree, token, options.ship, going, mine, [\n ...listed,\n ...refusedLines,\n ]);\n\n return outcome;\n}\n"]}
package/lib/sync.d.ts CHANGED
@@ -1,8 +1,9 @@
1
+ import { IAsset } from './assets.js';
1
2
  import { ICascadeCut, IIdleSkip } from './cascade.js';
2
3
  import { IBrokenLink, IGapOfVariant } from './catalog.js';
3
4
  import { ICompanion } from './companion.js';
4
5
  import { IConfig } from './config.js';
5
- import { IHookBinding, IMatcherDrift } from './hooks-map.js';
6
+ import { IBindResult, IHookBinding, IMatcherDrift } from './hooks-map.js';
6
7
  import { IPlanned } from './plan.js';
7
8
  export interface ISyncResult {
8
9
  readonly planned: readonly IPlanned[];
@@ -30,8 +31,9 @@ export interface ISyncResult {
30
31
  /**
31
32
  * Разложенные гарды, которых нет в настройке агента: файл лежит, а позвать его некому.
32
33
  *
33
- * Раскладку это не отбивает настройка принадлежит дереву, и пакет в неё не пишет, — но и
34
- * молчать нельзя: гард, который не зовут, неотличим от гарда, который всё пропускает.
34
+ * Раскладку это не отбивает: недостающую запись она дописывает сама и говорит, что дописала.
35
+ * Список остаётся тем, что видит планирование и сверка, они на диск не пишут, — а также
36
+ * деревом, чью настройку не разобрать: там запись по-прежнему делает рука.
35
37
  */
36
38
  readonly unbound: readonly IHookBinding[];
37
39
  /**
@@ -75,8 +77,38 @@ export interface ISyncResult {
75
77
  readonly namedCut: readonly ICascadeCut[];
76
78
  /** Файлы ресурсов, которых в наборе больше нет: их убирает дерево, пакет только называет. */
77
79
  readonly retired: readonly IRetiredFound[];
80
+ /**
81
+ * Надстройки, не подобранные ни к одному ресурсу пакета.
82
+ *
83
+ * Подбор идёт по идентификатору ресурса целиком, вместе с приставкой свойства: надстройка,
84
+ * чьё имя приставку потеряло или носит чужую, не привязывается ни к чему, и раскладка
85
+ * молча кладёт пакетный текст. Сверка этого не видит и видеть не может — она сравнивает
86
+ * разложенное с тем, что собирает сама, а неподобранное в сборку не берёт: обе стороны
87
+ * сходятся, и молчание читается как «всё применено».
88
+ */
89
+ readonly strayOverrides: readonly string[];
78
90
  readonly written: readonly string[];
91
+ /**
92
+ * Что запись объявления сделала с настройкой агента. `null` — раскладки не было: планирование
93
+ * и сверка на диск не пишут вовсе.
94
+ */
95
+ readonly bound: IBindResult | null;
79
96
  }
97
+ /**
98
+ * Тело ресурса: текст пакета, поверх него надстройка проекта, и уже потом подстановка значений.
99
+ *
100
+ * Порядок именно такой. Надстройка тоже пишется с дырками — иначе проект, дописавший раздел про
101
+ * свою главную ветку, зашил бы её имя в двух местах: в конфиге и в тексте.
102
+ */
103
+ /**
104
+ * Текст ресурса с наложенной надстройкой дерева — то, что ложится в дерево, без подстановки
105
+ * значений.
106
+ *
107
+ * Экспортируется потому, что о разложенном тексте спрашивает не одна раскладка: счёт долга
108
+ * привязок судит статьи компаньона против правила, и судить их по пакетной редакции значит
109
+ * называть долгом статьи, которых в разложенном правиле нет вовсе.
110
+ */
111
+ export declare function mergedBody(asset: IAsset, root: string): string;
80
112
  /** Разложенный раньше файл ресурса, снятого теперь каскадом, и причина, по которой он снят. */
81
113
  export interface ICutFound {
82
114
  /** Путь в дереве, где лежит файл. */