@rt-tools/agent-kit 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +8 -2
  2. package/assets/checks/board-long-work.github.mjs +101 -0
  3. package/assets/checks/board-runs.github.mjs +34 -0
  4. package/assets/checks/board.github.mjs +1 -1
  5. package/assets/checks/check-board.github.mjs +30 -2
  6. package/assets/checks/check-doc-paths.mjs +24 -5
  7. package/assets/checks/check-file-size.mjs +8 -2
  8. package/assets/checks/check-prose-style.mjs +10 -1
  9. package/assets/checks/check-reuse.mjs +4 -1
  10. package/assets/checks/check-schema-drift.mjs +65 -6
  11. package/assets/checks/rt-kit-checks.config.mjs +13 -0
  12. package/assets/checks/signals.mjs +41 -1
  13. package/assets/commands/next-session.md +16 -5
  14. package/assets/defaults/gate-map.sh +13 -0
  15. package/assets/defaults/project.sh +26 -0
  16. package/assets/defaults/shell.sh +18 -3
  17. package/assets/hooks/browser-guard-device-id.sh +42 -12
  18. package/assets/hooks/browser-guard-no-asking.sh +5 -1
  19. package/assets/hooks/dispatch.sh +40 -9
  20. package/assets/hooks/docs-guard.sh +10 -0
  21. package/assets/hooks/exam-guard.sh +66 -16
  22. package/assets/hooks/git-guard-delivery-draft.sh +78 -0
  23. package/assets/hooks/git-guard-delivery.sh +64 -122
  24. package/assets/hooks/git-guard-main.sh +39 -4
  25. package/assets/hooks/git-guard-push-tests.sh +71 -3
  26. package/assets/hooks/glossary-load.sh +23 -2
  27. package/assets/hooks/grill-gate.sh +62 -0
  28. package/assets/hooks/hook-input.sh +17 -6
  29. package/assets/hooks/rule-source-guard.sh +11 -0
  30. package/assets/hooks/stand-login-guard.sh +101 -0
  31. package/assets/hooks/write-targets.sh +37 -4
  32. package/assets/laws/autonomous-work.md +30 -0
  33. package/assets/laws/project-documentation.md +8 -0
  34. package/assets/laws/verifiability.md +12 -2
  35. package/assets/laws/work-conduct.md +59 -65
  36. package/assets/patterns/autonomous-work-run.md +105 -0
  37. package/assets/patterns/browser-verification-measure.md +41 -1
  38. package/assets/patterns/browser-verification-stand.md +57 -16
  39. package/assets/patterns/doc-style-human.md +75 -0
  40. package/assets/patterns/doc-style-write.md +16 -0
  41. package/assets/patterns/git-workflow-commit.azure.md +12 -0
  42. package/assets/patterns/git-workflow-commit.github.md +16 -3
  43. package/assets/patterns/git-workflow-commit.gitlab.md +12 -0
  44. package/assets/patterns/git-workflow-merge.md +8 -0
  45. package/assets/patterns/git-workflow-pr-ready.md +93 -0
  46. package/assets/patterns/git-workflow-pr.azure.md +1 -1
  47. package/assets/patterns/git-workflow-pr.github.md +1 -1
  48. package/assets/patterns/git-workflow-pr.gitlab.md +1 -1
  49. package/assets/patterns/task-flow-start.md +48 -48
  50. package/assets/patterns/testing-e2e.md +18 -8
  51. package/assets/patterns/ts-procedure.md +3 -2
  52. package/assets/pitfalls/task-flow.md +40 -40
  53. package/assets/rules/autonomous-work.md +92 -0
  54. package/assets/rules/browser-verification.md +35 -3
  55. package/assets/rules/deploy-flow.azure.md +7 -0
  56. package/assets/rules/deploy-flow.github.md +7 -0
  57. package/assets/rules/deploy-flow.gitlab.md +7 -0
  58. package/assets/rules/doc-style.md +54 -0
  59. package/assets/rules/git-workflow.azure.md +8 -0
  60. package/assets/rules/git-workflow.github.md +59 -63
  61. package/assets/rules/git-workflow.gitlab.md +8 -0
  62. package/assets/rules/reuse-first.md +25 -5
  63. package/assets/rules/styling-bem.md +8 -1
  64. package/assets/rules/task-flow.md +108 -109
  65. package/assets/rules/testing.md +21 -0
  66. package/assets/skills/agent-kit.md +72 -82
  67. package/lib/commands.d.ts.map +1 -1
  68. package/lib/commands.js +69 -2
  69. package/lib/commands.js.map +1 -1
  70. package/lib/enroll.d.ts.map +1 -1
  71. package/lib/enroll.js +1 -1
  72. package/lib/enroll.js.map +1 -1
  73. package/lib/observations.d.ts +10 -1
  74. package/lib/observations.d.ts.map +1 -1
  75. package/lib/observations.js +1 -0
  76. package/lib/observations.js.map +1 -1
  77. package/lib/override-marks.d.ts +24 -0
  78. package/lib/override-marks.d.ts.map +1 -0
  79. package/lib/override-marks.js +98 -0
  80. package/lib/override-marks.js.map +1 -0
  81. package/lib/push-gate.d.ts +14 -0
  82. package/lib/push-gate.d.ts.map +1 -0
  83. package/lib/push-gate.js +93 -0
  84. package/lib/push-gate.js.map +1 -0
  85. package/lib/shipment.d.ts.map +1 -1
  86. package/lib/shipment.js +1 -1
  87. package/lib/shipment.js.map +1 -1
  88. package/package.json +1 -1
  89. package/rt-tools-agent-kit-0.24.0.tgz +0 -0
  90. package/assets/laws/application/access.md +0 -34
  91. package/assets/laws/application/locales.md +0 -33
  92. package/assets/laws/application/search-visibility.md +0 -24
  93. package/assets/patterns/permissions-procedure.md +0 -71
  94. package/assets/patterns/seo-page.md +0 -104
  95. package/assets/patterns/seo-verify.md +0 -83
  96. package/assets/patterns/translations-content.md +0 -107
  97. package/assets/patterns/translations-key.md +0 -64
  98. package/assets/rules/permissions.md +0 -116
  99. package/assets/rules/seo.md +0 -139
  100. package/assets/rules/translations.md +0 -96
  101. package/rt-tools-agent-kit-0.22.0.tgz +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"shipment.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Сборка груза и команда отправки.\n *\n * Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без\n * замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево\n * переопределило. Отправка, построенная вокруг предложения, эти данные теряла.\n *\n * Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке\n * подменяется двойником.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport {\n CARGO_SCHEMA_VERSION,\n ICargoCount,\n ICargoOverride,\n IPostmortemItem,\n IPostmortemsCargo,\n IProposalItem,\n IProposalsCargo,\n ISummaryCargo,\n} from './cargo.js';\nimport { IEnvironment, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH, IConfig, readConfig } from './config.js';\nimport { IReadResult, ISummary, readObservations, summarize } from './observations.js';\nimport { ILeak, IProposal, leaksIn, markSent, marksOf, nearestMissing, readProposals, TO_PACKAGE } from './proposals.js';\nimport { IShipment, IShipped, readToken, TShip } from './ship.js';\nimport { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';\nimport { byText } from './order.js';\n\n/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */\nconst SLUG_LENGTH: number = 12;\n\n/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */\nconst REFUSED: number = 1;\n\n/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */\nconst TOKEN_REFUSED: number = 401;\n\n/** Чем отправка живёт: чем отправлять, чем себя выдаёт дерево и за какой отрезок сводка. */\nexport interface IShipOptions {\n /** Печатать, что уехало бы, и не отправлять ничего. */\n readonly dryRun: boolean;\n readonly ship: TShip;\n /** Адрес удалённого репозитория этого дерева: из него считается признак. */\n readonly remote: string;\n /** Имена удалённых репозиториев. Нужны отказу: выбор между несколькими делает человек. */\n readonly remotes?: readonly string[];\n /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */\n readonly today: string;\n readonly days: number;\n}\n\n/**\n * Адрес удалённого репозитория, приведённый к одному виду.\n *\n * Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они\n * ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а\n * непривёденные формы дали бы два.\n */\nexport function remoteMarkOf(remote: string): string {\n const named: string = remote\n .trim()\n .replace(/\\.git$/, '')\n .replace(/^[a-z+]+:\\/\\//i, '')\n .replace(/^[^@/]+@/, '')\n .replace(/:/g, '/');\n let end: number = named.length;\n\n while (end > 0 && named[end - 1] === '/') {\n end -= 1;\n }\n\n return named.slice(0, end).toLowerCase();\n}\n\n/**\n * Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.\n *\n * Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих\n * копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе\n * все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.\n */\nexport function treeSlugOf(remote: string, spoken: string): string {\n const mark: string = remoteMarkOf(remote);\n\n return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;\n}\n\n/**\n * Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и\n * наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.\n *\n * Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,\n * а не наблюдения.\n */\nfunction knownCounts(counts: readonly ICargoCount[], known: ReadonlySet<string>): readonly ICargoCount[] {\n return counts.filter((count: ICargoCount): boolean => known.has(count.name));\n}\n\n/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */\nexport function summaryCargo(\n summary: ISummary,\n tree: string,\n overrides: readonly ICargoOverride[],\n unpicked: readonly string[],\n known: ReadonlySet<string>\n): ISummaryCargo {\n return {\n tree,\n unpicked,\n overrides,\n schema: CARGO_SCHEMA_VERSION,\n days: summary.days,\n sessions: summary.sessions,\n loads: knownCounts(summary.loads, known),\n denials: knownCounts(summary.denials, known),\n // Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`\n // или `browser` — слова пакета, одинаковые у всех деревьев.\n kinds: summary.kinds,\n guards: knownCounts(summary.guards, known),\n unused: summary.unused,\n versions: summary.versions,\n total: summary.total,\n };\n}\n\n/**\n * Разборы происшествий, как они лежат на дереве.\n *\n * Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на\n * один вопрос, ради которого его читают.\n */\nexport function readPostmortems(root: string, dir: string): IPostmortemItem[] {\n const path: string = join(root, dir);\n\n if (!existsSync(path)) {\n return [];\n }\n\n return (\n readdirSync(path)\n // Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм\n // промаха, — и в приёме встало бы записью, которой нечего сказать.\n .filter((file: string): boolean => file.endsWith('.md') && file !== 'README.md')\n .sort(byText)\n .map((file: string): IPostmortemItem => ({ file, text: readFileSync(join(path, file), 'utf8') }))\n );\n}\n\n/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */\nexport function shipmentsOf(summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): readonly IShipment[] {\n return [\n { kind: 'сводка', operation: 'summary', body: summary },\n ...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),\n ...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),\n ];\n}\n\n/**\n * Что в грузе называет дерево, из которого он уезжает.\n *\n * Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству\n * называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его\n * отправку.\n */\nexport function leaksOfCargo(summary: ISummaryCargo, proposals: readonly IProposal[], marks: readonly string[]): string[] {\n const inSummary: string[] = leaksIn(JSON.stringify(summary, null, 1), marks).map(\n (leak: ILeak): string => ` сводка: ${leak.why}\\n ${leak.text}`\n );\n const inProposals: string[] = proposals.flatMap((entry: IProposal): string[] =>\n leaksIn(entry.body, marks).map(\n (leak: ILeak): string => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\\n ${leak.text}`\n )\n );\n\n return [...inSummary, ...inProposals];\n}\n\n/** Отказ строками. */\nfunction refusal(...lines: readonly string[]): IOutcomeOfCommand {\n return { code: REFUSED, lines: [...lines] };\n}\n\n/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */\nfunction describe(shipment: IShipment, summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): string {\n if (shipment.operation === 'proposals') {\n return `предложений ${proposals.items.length}`;\n }\n if (shipment.operation === 'postmortems') {\n return `разборов ${postmortems.items.length}`;\n }\n\n return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;\n}\n\n/** Блок, который не уехал, и причина этого. */\ninterface IRefusedProposal {\n readonly proposal: IProposal;\n readonly why: string;\n}\n\n/**\n * Отметка об отбое: отбитый блок остаётся на диске и несёт причину.\n *\n * Удалённый блок пишется следующим заходом заново — следа разбора нет, и повтор возвращается.\n * Отметка держит и то и другое: видно, что разбор был, и видно, почему он не стал правкой.\n */\nfunction markRefused(root: string, refused: readonly IRefusedProposal[]): void {\n for (const one of refused) {\n const path: string = join(root, one.proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), one.proposal, one.why, 'отбито'), 'utf8');\n }\n}\n\n/**\n * Перечень груза: что именно уедет и куда — строками, которые читаются до самой отправки.\n *\n * Печатается он обоими прогонами, и разделены они не окончанием глагола, а первой строкой:\n * «уехало» и «уехало бы» отличаются двумя буквами в хвосте, а строки под ними одинаковы до\n * знака. Прочитанный не тем прогоном, вывод сухого читается сделанной работой — и наоборот,\n * настоящая отправка читается пробой, после которой ничего не делают.\n */\nfunction manifest(\n going: readonly IShipment[],\n summary: ISummaryCargo,\n proposals: IProposalsCargo,\n postmortems: IPostmortemsCargo\n): readonly string[] {\n return going.map((shipment: IShipment): string => ` ${shipment.operation} — ${describe(shipment, summary, proposals, postmortems)}`);\n}\n\n/** Пометка об отправке: по ней предложение второй раз не уезжает. */\nfunction markProposals(root: string, proposals: readonly IProposal[], shipped: IShipped): void {\n const mark: string = `приём:${shipped.accepted?.month ?? 'принято'}`;\n\n for (const proposal of proposals) {\n const path: string = join(root, proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');\n }\n}\n\n/**\n * Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт\n * легшего и уже лежавшего.\n *\n * Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже\n * лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку\n * прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.\n */\nfunction accepted(shipped: IShipped): string {\n if (!shipped.accepted) {\n return 'принято';\n }\n\n const record: string = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;\n const added: number | undefined = shipped.accepted.added;\n\n return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;\n}\n\n/**\n * Отказ, когда признак дерева считать не из чего. Случая два, и говорят они разное: репозитория\n * нет вовсе — считать не из чего; репозиториев несколько и `origin` среди них нет — адрес есть, а\n * выбрать его может только человек. Угаданное неверно сливает в сводке приёма два дерева в одно,\n * и увидеть это нечем.\n */\nfunction noTreeMark(names: readonly string[]): IOutcomeOfCommand {\n if (names.length > 1) {\n return refusal(\n `признак дерева не считается: удалённых репозиториев несколько, а \\`origin\\` среди них нет — ${names.join(', ')}`,\n `переименуй тот, что называет это дерево, в \\`origin\\` — либо назови признак ключом \\`tree\\` в ${CONFIG_PATH}`\n );\n }\n\n return refusal(\n 'признак дерева не считается: удалённого репозитория нет',\n `назови его ключом \\`tree\\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`\n );\n}\n\n/**\n * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе\n * увозит половину и не оставляет следа, по которому видно, какую именно.\n */\nasync function send(\n root: string,\n intake: string,\n tree: string,\n token: string,\n ship: TShip,\n going: readonly IShipment[],\n proposals: readonly IProposal[],\n listed: readonly string[]\n): Promise<IOutcomeOfCommand> {\n // Что уедет, названо до того, как уехало: отказ на втором запросе иначе оставляет человека с\n // одной строкой о нём и без перечня, из которого видно, чего этот отказ стоил.\n const head: readonly string[] = [`ОТПРАВКА — груз уходит в ${intake}, дерево ${tree}:`, ...listed];\n const done: string[] = [];\n\n for (const shipment of going) {\n const shipped: IShipped = await ship(intake, token, shipment);\n\n if (!shipped.ok) {\n return {\n code: REFUSED,\n lines: [\n ...head,\n ...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),\n `${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,\n ...(shipped.status === TOKEN_REFUSED\n ? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']\n : []),\n ],\n };\n }\n\n done.push(` ${shipment.kind} → ${accepted(shipped)}`);\n\n if (shipment.operation === 'proposals') {\n markProposals(root, proposals, shipped);\n }\n }\n\n return { code: 0, lines: [...head, 'уехало:', ...done] };\n}\n\n/**\n * Отправка груза в приём.\n *\n * Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит\n * отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева\n * незачем.\n */\nexport async function propose(env: IEnvironment, options: IShipOptions): Promise<IOutcomeOfCommand> {\n const { root, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n\n if (!config) {\n return refusal(`конфига нет: ${CONFIG_PATH} — заводится \\`agent-kit init\\``);\n }\n if (!config.observe) {\n return {\n code: 0,\n lines: [\n 'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',\n 'выключатель гасит и отправку целиком, вместе со снимком надстроек',\n ],\n };\n }\n if (!config.intake) {\n return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \\`intake\\` в ${CONFIG_PATH}`);\n }\n\n const tree: string = treeSlugOf(options.remote, config.tree);\n if (!tree) {\n return noTreeMark(options.remotes ?? []);\n }\n\n const token: string = readToken(root, config.token);\n if (!token) {\n return refusal(\n 'токена дерева нет: груз остался неотправленным',\n `файл с ним называется ключом \\`token\\` в ${CONFIG_PATH} и лежит вне дерева`,\n 'выдаётся токен командой приёма — `tree:add` или `tree:token`'\n );\n }\n\n const read: IReadResult = readObservations(root, options.today, options.days);\n const summary: ISummary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);\n const cargo: ISummaryCargo = summaryCargo(\n summary,\n tree,\n treeSnapshot(config, assetsDir, root),\n unpickedOf(config, assetsDir),\n packagedNames(config, assetsDir)\n );\n\n const ready: readonly IProposal[] = readProposals(root).filter(\n (entry: IProposal): boolean => entry.address === TO_PACKAGE && !entry.sent\n );\n // Блок, не назвавший ближайшего утверждения ресурса, не уезжает: разбор, кончившийся ещё\n // одной статьёй о том, о чём статья уже стоит, снаружи неотличим от разбора, кончившегося\n // исправлением. Отбивается он поимённо, а остальные едут: один непрочитанный ресурс не\n // повод задержать чужую работу.\n const refused: readonly IRefusedProposal[] = ready\n .map((entry: IProposal): IRefusedProposal => ({ proposal: entry, why: nearestMissing(entry, assetsDir) }))\n .filter((entry: IRefusedProposal): boolean => Boolean(entry.why));\n const mine: readonly IProposal[] = ready.filter(\n (entry: IProposal): boolean => !refused.some((one: IRefusedProposal): boolean => one.proposal === entry)\n );\n const proposals: IProposalsCargo = {\n tree,\n schema: CARGO_SCHEMA_VERSION,\n items: mine.map((entry: IProposal): IProposalItem => ({ text: entry.body, address: entry.address, resource: entry.resource })),\n };\n const postmortems: IPostmortemsCargo = { tree, schema: CARGO_SCHEMA_VERSION, items: readPostmortems(root, config.postmortems) };\n\n // Проверка на адрес дерева судит все готовые блоки, а не одни уезжающие: отбитый по цитате\n // лежит на диске и уедет, как только его починят, — а найденная в нём утечка отбивает\n // отправку целиком и должна называться сразу.\n const leaked: readonly string[] = leaksOfCargo(cargo, ready, marksOf(root, options.remote));\n if (leaked.length) {\n return refusal(\n `отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`,\n ...leaked,\n 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»'\n );\n }\n\n const going: readonly IShipment[] = shipmentsOf(cargo, proposals, postmortems);\n\n const listed: readonly string[] = manifest(going, cargo, proposals, postmortems);\n // Отбитое называется обоими прогонами: сухой показывает, что уехало бы, — и отбитое к этому\n // относится наравне с уезжающим.\n const refusedLines: readonly string[] = refused.map(\n (one: IRefusedProposal): string => ` отбито ${one.proposal.file}:${one.proposal.line} — ${one.why}`\n );\n\n if (options.dryRun) {\n return {\n code: 0,\n lines: [\n 'СУХОЙ ПРОГОН — наружу не ушло ничего, отметок об отправке не поставлено',\n `уехало бы в ${config.intake}, дерево ${tree}:`,\n ...listed,\n ...refusedLines,\n ...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),\n 'отправляет это тот же вызов без `--dry-run`',\n ],\n };\n }\n\n // Отметка ставится до отправки: отбитое наружу не едет вовсе, и ждать ответа приёма ей\n // незачем.\n markRefused(root, refused);\n\n const outcome: IOutcomeOfCommand = await send(root, config.intake, tree, token, options.ship, going, mine, [\n ...listed,\n ...refusedLines,\n ]);\n\n return outcome;\n}\n"]}
1
+ {"version":3,"file":"shipment.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Сборка груза и команда отправки.\n *\n * Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без\n * замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево\n * переопределило. Отправка, построенная вокруг предложения, эти данные теряла.\n *\n * Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке\n * подменяется двойником.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport {\n CARGO_SCHEMA_VERSION,\n ICargoCount,\n ICargoOverride,\n IPostmortemItem,\n IPostmortemsCargo,\n IProposalItem,\n IProposalsCargo,\n ISummaryCargo,\n} from './cargo.js';\nimport { IEnvironment, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH, IConfig, readConfig } from './config.js';\nimport { IReadResult, ISummary, readObservations, summarize } from './observations.js';\nimport { ILeak, IProposal, leaksIn, markSent, marksOf, nearestMissing, readProposals, TO_PACKAGE } from './proposals.js';\nimport { IShipment, IShipped, readToken, TShip } from './ship.js';\nimport { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';\nimport { byText } from './order.js';\n\n/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */\nconst SLUG_LENGTH: number = 12;\n\n/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */\nconst REFUSED: number = 1;\n\n/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */\nconst TOKEN_REFUSED: number = 401;\n\n/** Чем отправка живёт: чем отправлять, чем себя выдаёт дерево и за какой отрезок сводка. */\nexport interface IShipOptions {\n /** Печатать, что уехало бы, и не отправлять ничего. */\n readonly dryRun: boolean;\n readonly ship: TShip;\n /** Адрес удалённого репозитория этого дерева: из него считается признак. */\n readonly remote: string;\n /** Имена удалённых репозиториев. Нужны отказу: выбор между несколькими делает человек. */\n readonly remotes?: readonly string[];\n /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */\n readonly today: string;\n readonly days: number;\n}\n\n/**\n * Адрес удалённого репозитория, приведённый к одному виду.\n *\n * Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они\n * ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а\n * непривёденные формы дали бы два.\n */\nexport function remoteMarkOf(remote: string): string {\n const named: string = remote\n .trim()\n .replace(/\\.git$/, '')\n .replace(/^[a-z+]+:\\/\\//i, '')\n .replace(/^[^@/]+@/, '')\n .replace(/:/g, '/');\n let end: number = named.length;\n\n while (end > 0 && named[end - 1] === '/') {\n end -= 1;\n }\n\n return named.slice(0, end).toLowerCase();\n}\n\n/**\n * Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.\n *\n * Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих\n * копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе\n * все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.\n */\nexport function treeSlugOf(remote: string, spoken: string): string {\n const mark: string = remoteMarkOf(remote);\n\n return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;\n}\n\n/**\n * Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и\n * наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.\n *\n * Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,\n * а не наблюдения.\n */\nfunction knownCounts(counts: readonly ICargoCount[], known: ReadonlySet<string>): readonly ICargoCount[] {\n return counts.filter((count: ICargoCount): boolean => known.has(count.name));\n}\n\n/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */\nexport function summaryCargo(\n summary: ISummary,\n tree: string,\n overrides: readonly ICargoOverride[],\n unpicked: readonly string[],\n known: ReadonlySet<string>\n): ISummaryCargo {\n return {\n tree,\n unpicked,\n overrides,\n schema: CARGO_SCHEMA_VERSION,\n days: summary.days,\n sessions: summary.sessions,\n loads: knownCounts(summary.loads, known),\n denials: knownCounts(summary.denials, known),\n // Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`\n // или `browser` — слова пакета, одинаковые у всех деревьев.\n kinds: summary.kinds,\n guards: knownCounts(summary.guards, known),\n unused: summary.unused,\n versions: summary.versions,\n total: summary.total,\n };\n}\n\n/**\n * Разборы происшествий, как они лежат на дереве.\n *\n * Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на\n * один вопрос, ради которого его читают.\n */\nexport function readPostmortems(root: string, dir: string): IPostmortemItem[] {\n const path: string = join(root, dir);\n\n if (!existsSync(path)) {\n return [];\n }\n\n return (\n readdirSync(path)\n // Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм\n // промаха, — и в приёме встало бы записью, которой нечего сказать.\n .filter((file: string): boolean => file.endsWith('.md') && file !== 'README.md')\n .sort(byText)\n .map((file: string): IPostmortemItem => ({ file, text: readFileSync(join(path, file), 'utf8') }))\n );\n}\n\n/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */\nexport function shipmentsOf(summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): readonly IShipment[] {\n return [\n { kind: 'сводка', operation: 'summary', body: summary },\n ...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),\n ...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),\n ];\n}\n\n/**\n * Что в грузе называет дерево, из которого он уезжает.\n *\n * Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству\n * называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его\n * отправку.\n */\nexport function leaksOfCargo(summary: ISummaryCargo, proposals: readonly IProposal[], marks: readonly string[]): string[] {\n const inSummary: string[] = leaksIn(JSON.stringify(summary, null, 1), marks).map(\n (leak: ILeak): string => ` сводка: ${leak.why}\\n ${leak.text}`\n );\n const inProposals: string[] = proposals.flatMap((entry: IProposal): string[] =>\n leaksIn(entry.body, marks).map(\n (leak: ILeak): string => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\\n ${leak.text}`\n )\n );\n\n return [...inSummary, ...inProposals];\n}\n\n/** Отказ строками. */\nfunction refusal(...lines: readonly string[]): IOutcomeOfCommand {\n return { code: REFUSED, lines: [...lines] };\n}\n\n/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */\nfunction describe(shipment: IShipment, summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): string {\n if (shipment.operation === 'proposals') {\n return `предложений ${proposals.items.length}`;\n }\n if (shipment.operation === 'postmortems') {\n return `разборов ${postmortems.items.length}`;\n }\n\n return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;\n}\n\n/** Блок, который не уехал, и причина этого. */\ninterface IRefusedProposal {\n readonly proposal: IProposal;\n readonly why: string;\n}\n\n/**\n * Отметка об отбое: отбитый блок остаётся на диске и несёт причину.\n *\n * Удалённый блок пишется следующим заходом заново — следа разбора нет, и повтор возвращается.\n * Отметка держит и то и другое: видно, что разбор был, и видно, почему он не стал правкой.\n */\nfunction markRefused(root: string, refused: readonly IRefusedProposal[]): void {\n for (const one of refused) {\n const path: string = join(root, one.proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), one.proposal, one.why, 'отбито'), 'utf8');\n }\n}\n\n/**\n * Перечень груза: что именно уедет и куда — строками, которые читаются до самой отправки.\n *\n * Печатается он обоими прогонами, и разделены они не окончанием глагола, а первой строкой:\n * «уехало» и «уехало бы» отличаются двумя буквами в хвосте, а строки под ними одинаковы до\n * знака. Прочитанный не тем прогоном, вывод сухого читается сделанной работой — и наоборот,\n * настоящая отправка читается пробой, после которой ничего не делают.\n */\nfunction manifest(\n going: readonly IShipment[],\n summary: ISummaryCargo,\n proposals: IProposalsCargo,\n postmortems: IPostmortemsCargo\n): readonly string[] {\n return going.map((shipment: IShipment): string => ` ${shipment.operation} — ${describe(shipment, summary, proposals, postmortems)}`);\n}\n\n/** Пометка об отправке: по ней предложение второй раз не уезжает. */\nfunction markProposals(root: string, proposals: readonly IProposal[], shipped: IShipped): void {\n const mark: string = `приём:${shipped.accepted?.month ?? 'принято'}`;\n\n for (const proposal of proposals) {\n const path: string = join(root, proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');\n }\n}\n\n/**\n * Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт\n * легшего и уже лежавшего.\n *\n * Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже\n * лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку\n * прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.\n */\nfunction accepted(shipped: IShipped): string {\n if (!shipped.accepted) {\n return 'принято';\n }\n\n const record: string = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;\n const added: number | undefined = shipped.accepted.added;\n\n return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;\n}\n\n/**\n * Отказ, когда признак дерева считать не из чего. Случая два, и говорят они разное: репозитория\n * нет вовсе — считать не из чего; репозиториев несколько и `origin` среди них нет — адрес есть, а\n * выбрать его может только человек. Угаданное неверно сливает в сводке приёма два дерева в одно,\n * и увидеть это нечем.\n */\nfunction noTreeMark(names: readonly string[]): IOutcomeOfCommand {\n if (names.length > 1) {\n return refusal(\n `признак дерева не считается: удалённых репозиториев несколько, а \\`origin\\` среди них нет — ${names.join(', ')}`,\n `переименуй тот, что называет это дерево, в \\`origin\\` — либо назови признак ключом \\`tree\\` в ${CONFIG_PATH}`\n );\n }\n\n return refusal(\n 'признак дерева не считается: удалённого репозитория нет',\n `назови его ключом \\`tree\\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`\n );\n}\n\n/**\n * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе\n * увозит половину и не оставляет следа, по которому видно, какую именно.\n */\nasync function send(\n root: string,\n intake: string,\n tree: string,\n token: string,\n ship: TShip,\n going: readonly IShipment[],\n proposals: readonly IProposal[],\n listed: readonly string[]\n): Promise<IOutcomeOfCommand> {\n // Что уедет, названо до того, как уехало: отказ на втором запросе иначе оставляет человека с\n // одной строкой о нём и без перечня, из которого видно, чего этот отказ стоил.\n const head: readonly string[] = [`ОТПРАВКА — груз уходит в ${intake}, дерево ${tree}:`, ...listed];\n const done: string[] = [];\n\n for (const shipment of going) {\n const shipped: IShipped = await ship(intake, token, shipment);\n\n if (!shipped.ok) {\n return {\n code: REFUSED,\n lines: [\n ...head,\n ...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),\n `${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,\n ...(shipped.status === TOKEN_REFUSED\n ? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']\n : []),\n ],\n };\n }\n\n done.push(` ${shipment.kind} → ${accepted(shipped)}`);\n\n if (shipment.operation === 'proposals') {\n markProposals(root, proposals, shipped);\n }\n }\n\n return { code: 0, lines: [...head, 'уехало:', ...done] };\n}\n\n/**\n * Отправка груза в приём.\n *\n * Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит\n * отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева\n * незачем.\n */\nexport async function propose(env: IEnvironment, options: IShipOptions): Promise<IOutcomeOfCommand> {\n const { root, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n\n if (!config) {\n return refusal(`конфига нет: ${CONFIG_PATH} — заводится \\`agent-kit init\\``);\n }\n if (!config.observe) {\n return {\n code: 0,\n lines: [\n 'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',\n 'выключатель гасит и отправку целиком, вместе со снимком надстроек',\n ],\n };\n }\n if (!config.intake) {\n return refusal(\n 'отправлять некуда: адреса приёма нет',\n `он объявляется ключом \\`intake\\` в ${CONFIG_PATH}, а само значение берётся у владельца приёма — вместе с кодом приглашения`\n );\n }\n\n const tree: string = treeSlugOf(options.remote, config.tree);\n if (!tree) {\n return noTreeMark(options.remotes ?? []);\n }\n\n const token: string = readToken(root, config.token);\n if (!token) {\n return refusal(\n 'токена дерева нет: груз остался неотправленным',\n `файл с ним называется ключом \\`token\\` в ${CONFIG_PATH} и лежит вне дерева`,\n 'выдаётся токен командой приёма — `tree:add` или `tree:token`'\n );\n }\n\n const read: IReadResult = readObservations(root, options.today, options.days);\n const summary: ISummary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);\n const cargo: ISummaryCargo = summaryCargo(\n summary,\n tree,\n treeSnapshot(config, assetsDir, root),\n unpickedOf(config, assetsDir),\n packagedNames(config, assetsDir)\n );\n\n const ready: readonly IProposal[] = readProposals(root).filter(\n (entry: IProposal): boolean => entry.address === TO_PACKAGE && !entry.sent\n );\n // Блок, не назвавший ближайшего утверждения ресурса, не уезжает: разбор, кончившийся ещё\n // одной статьёй о том, о чём статья уже стоит, снаружи неотличим от разбора, кончившегося\n // исправлением. Отбивается он поимённо, а остальные едут: один непрочитанный ресурс не\n // повод задержать чужую работу.\n const refused: readonly IRefusedProposal[] = ready\n .map((entry: IProposal): IRefusedProposal => ({ proposal: entry, why: nearestMissing(entry, assetsDir) }))\n .filter((entry: IRefusedProposal): boolean => Boolean(entry.why));\n const mine: readonly IProposal[] = ready.filter(\n (entry: IProposal): boolean => !refused.some((one: IRefusedProposal): boolean => one.proposal === entry)\n );\n const proposals: IProposalsCargo = {\n tree,\n schema: CARGO_SCHEMA_VERSION,\n items: mine.map((entry: IProposal): IProposalItem => ({ text: entry.body, address: entry.address, resource: entry.resource })),\n };\n const postmortems: IPostmortemsCargo = { tree, schema: CARGO_SCHEMA_VERSION, items: readPostmortems(root, config.postmortems) };\n\n // Проверка на адрес дерева судит все готовые блоки, а не одни уезжающие: отбитый по цитате\n // лежит на диске и уедет, как только его починят, — а найденная в нём утечка отбивает\n // отправку целиком и должна называться сразу.\n const leaked: readonly string[] = leaksOfCargo(cargo, ready, marksOf(root, options.remote));\n if (leaked.length) {\n return refusal(\n `отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`,\n ...leaked,\n 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»'\n );\n }\n\n const going: readonly IShipment[] = shipmentsOf(cargo, proposals, postmortems);\n\n const listed: readonly string[] = manifest(going, cargo, proposals, postmortems);\n // Отбитое называется обоими прогонами: сухой показывает, что уехало бы, — и отбитое к этому\n // относится наравне с уезжающим.\n const refusedLines: readonly string[] = refused.map(\n (one: IRefusedProposal): string => ` отбито ${one.proposal.file}:${one.proposal.line} — ${one.why}`\n );\n\n if (options.dryRun) {\n return {\n code: 0,\n lines: [\n 'СУХОЙ ПРОГОН — наружу не ушло ничего, отметок об отправке не поставлено',\n `уехало бы в ${config.intake}, дерево ${tree}:`,\n ...listed,\n ...refusedLines,\n ...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),\n 'отправляет это тот же вызов без `--dry-run`',\n ],\n };\n }\n\n // Отметка ставится до отправки: отбитое наружу не едет вовсе, и ждать ответа приёма ей\n // незачем.\n markRefused(root, refused);\n\n const outcome: IOutcomeOfCommand = await send(root, config.intake, tree, token, options.ship, going, mine, [\n ...listed,\n ...refusedLines,\n ]);\n\n return outcome;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rt-tools/agent-kit",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Переносимый слой правил для агента: законы, хуки, проверки и агенты, раскладываемые в репозиторий одной командой",
5
5
  "author": "RT Team",
6
6
  "license": "Apache-2.0",
Binary file
@@ -1,34 +0,0 @@
1
- # Закон о доступе
2
-
3
- Кто что может делать: вход владельца, права на действия, поведение публичных путей.
4
- Правила держат все процедуры контракта и все разделы админки.
5
-
6
- ## Терминология
7
-
8
- | Термин | Определение |
9
- | -------------- | ------------------------------------------------------------------------------------------- |
10
- | Право | Пара «ресурс и действие», например `bookings:manage`. Не роль: роль набирается из прав |
11
- | Пресет | Именованный набор прав, который выдаётся пользователю целиком |
12
- | Оверрайд | Точечная правка права поверх пресета для одного пользователя |
13
- | Публичный путь | Процедура, доступная гостю без входа. Публичность объявляется, а не получается по умолчанию |
14
-
15
- ## Статьи
16
-
17
- - **Каждая процедура объявляет свой доступ, и объявление ровно одно.** Процедура без
18
- объявления или с двумя объявлениями не даёт приложению подняться.
19
- - **Доступов четыре: по праву, любому вошедшему, публично и публично с чтением входа.**
20
- Последний отдаёт вошедшему больше, чем гостю, — так админ видит скрытые объекты в общем
21
- списке.
22
- - **Права пользователя — это права пресета, поверх которых применены его оверрайды.**
23
- - **Запрос без входа туда, где вход нужен, отбивается как неаутентифицированный, а вход
24
- без нужного права — как отказ в доступе.** Это разные ответы: первый лечится входом,
25
- второй — нет.
26
- - **Право проверяется до тела процедуры.** Обработчик не решает, пускать ли вызывающего.
27
- - **Публичность объявляется с причиной.** Причина живёт в коде рядом с объявлением и
28
- наружу не уходит.
29
- - **Админка не показывает разделы, на которые у владельца нет права.**
30
- - **Адрес раздела закрыт тем же правом, что и пункт меню.** Иначе скрытый пункт закрывает
31
- раздел лишь на вид: адрес открывается по прямой ссылке.
32
- - **Пока права не получены, админка ничего не прячет.** Пустая шапка после сетевого сбоя
33
- выглядит как сломанная админка и не оставляет выхода; запрос без права всё равно
34
- отобьётся на сервере.
@@ -1,33 +0,0 @@
1
- # Закон о локалях и переводах
2
-
3
- Как в системе устроены языки: адреса публичного сайта, подписи интерфейса, переводы
4
- контента и язык админки. Правила держат сайт, админку, письма и документ-подтверждение
5
- одновременно.
6
-
7
- ## Терминология
8
-
9
- | Термин | Определение |
10
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
11
- | Локаль сайта | Один из восьми языков, на которых существует публичный сайт. Не то же, что валюта показа: валюта у локали лишь начальная и меняется гостем |
12
- | Локаль ввода | Язык, на котором владелец написал текст объекта. Задаёт истину: остальные семь считаются производными |
13
- | Словарь | Набор подписей интерфейса для одной локали. Живёт в репозитории, а не в базе |
14
- | Перевод контента | Текст объекта на локали, отличной от локали ввода. Живёт в базе и заполняется системой |
15
-
16
- ## Статьи
17
-
18
- - **Локаль по умолчанию отдаётся из корня, остальные семь — из-под префикса пути.**
19
- - **Видимый человеку текст интерфейса берётся из словарей и заводится во всех восьми
20
- локалях.** Ключ, потерянный в одной локали, гость видит на кнопке как есть.
21
- - **Пустой перевод считается пропуском, а не переводом.** На экране он выглядит как
22
- задуманный: кнопка без подписи, заголовок без текста.
23
- - **Переводы контента производные: владелец пишет текст на своём языке, остальные семь
24
- локалей заполняет система при сохранении.** Табов локалей в формах нет — правка вручную
25
- всё равно была бы перезаписана.
26
- - **Отказ перевода сохранение не срывает.** Текст на локали ввода записывается, прежние
27
- переводы остаются, и владелец видит предупреждение, а не сообщение об успехе.
28
- - **Локаль, для которой перевода не пришло, сохраняет прежнее значение.** Половина ответа
29
- лучше пустого описания на витрине.
30
- - **Язык админки выбирает владелец, и выбор живёт в его профиле.** Он же уходит в локаль
31
- ввода при сохранении объекта.
32
- - **У локали есть начальная валюта показа.** Дальше валюту выбирает гость, и его выбор
33
- сильнее умолчания локали.
@@ -1,24 +0,0 @@
1
- # Закон о видимости в поиске
2
-
3
- Как публичный сайт выглядит для поисковика. Страница живёт в нескольких языках под одним
4
- доменом, и от разметки зависит, считает поисковик эти адреса одной страницей на разных
5
- языках или дублями друг друга. Ошибка здесь не видна ни в сборке, ни в браузере: она
6
- проявляется падением выдачи через недели.
7
-
8
- ## Статьи
9
-
10
- - **У страницы один канонический адрес — её собственный, на её языке.** Общий канонический
11
- адрес на все языки означает, что остальные версии объявлены копиями и в выдачу не попадут.
12
- - **Разметка страницы при переходе переписывается, а не накапливается.** Иначе на второй
13
- открытой странице остаётся описание первой, и два адреса объявляют один канонический.
14
- - **Языковая версия объявляется поисковику только тогда, когда её перевод готов.** Позвать
15
- поисковика на страницу, где текст остался на чужом языке, — значит отдать дубль под
16
- неверным языковым тегом.
17
- - **Страница объявляет себя структурированными данными.** Без них поисковик разбирает
18
- содержимое догадками.
19
- - **Карта сайта строится из живых данных, а не из файла.** Файл устаревает молча в тот же
20
- день, когда добавили страницу.
21
- - **Прежний адрес страницы ведёт на новый постоянным перенаправлением.** Смена адреса без
22
- перенаправления обнуляет накопленный вес страницы.
23
- - **Перенаправление с прежнего адреса живёт в кэше наравне со страницами.** Некэшируемое
24
- перенаправление не вытесняет из кэша старую страницу, и гость видит её ещё сутки.
@@ -1,71 +0,0 @@
1
- ---
2
- name: permissions-procedure
3
- kind: pattern
4
- rule: permissions
5
- description: Паттерн правила permissions. Брать при заведении процедуры Connect и при закрытии раздела админки — готовые декораторы доступа, отбивка без входа и без права, декларация пункта меню с правом и флагом. Не брать для устройства самого меню — это правило navigation.
6
- ---
7
-
8
- # Объявление доступа
9
-
10
- Паттерн правила `permissions`. Что при этом должно быть верно — закон
11
- `docs/constitution/application/access.md`.
12
-
13
- ## Когда брать
14
-
15
- - Заводится процедура Connect.
16
- - Раздел админки закрывается правом.
17
- - Процедура должна отвечать гостю.
18
-
19
- ## Декоратор на классе процедуры
20
-
21
- Объявление ровно одно; без него приложение не поднимается:
22
-
23
- ```typescript
24
- @Injectable()
25
- @ConnectProcedure()
26
- @RequiresPermission('chat:manage')
27
- export class LinkBookingProcedure implements IConnectProcedure<typeof ChatService.method.linkBooking> {
28
- public readonly method: typeof ChatService.method.linkBooking = ChatService.method.linkBooking;
29
- }
30
- ```
31
-
32
- | Декоратор | Кому доступно |
33
- | -------------------------------------------- | ------------------------------------------------- |
34
- | `@RequiresPermission('<ресурс>:<действие>')` | вошедшему с этим правом |
35
- | `@RequiresAuth('<причина>')` | любому вошедшему; так живут профиль и выбор языка |
36
- | `@PublicProcedure('<причина>')` | гостю без входа |
37
- | `@OptionalAuthProcedure('<причина>')` | гостю, но токен читается, если он есть |
38
-
39
- Аргумент — причина для читателя кода. Ни в ответ, ни в лог она не уходит.
40
-
41
- ## Отбивка
42
-
43
- Перехватчик отвечает до тела процедуры:
44
-
45
- - нет входа там, где вход нужен, — `Code.Unauthenticated`;
46
- - вход есть, права нет — `Code.PermissionDenied`;
47
- - процедура, о которой перехватчик ничего не знает, — тоже отказ в доступе, а не пропуск.
48
-
49
- Обработчик решения о допуске не принимает.
50
-
51
- ## Раздел админки
52
-
53
- Пункт меню и адрес закрываются одной декларацией в
54
- `libs/admin/common/container/util`: шапка берёт из неё подписи и адреса, гвард — права.
55
- Второго объявления этой связи не заводится.
56
-
57
- Гейтинг двухслойный: право пользователя и флаг раздела. Пункт с флагом объявляется без прав и
58
- без адреса — право открывает экран, а экрана нет. Появится экран — флаг снимается, права
59
- добавляются.
60
-
61
- ## Частые промахи
62
-
63
- - Два объявления доступа на одной процедуре: приложение не поднимется, и увидено это будет
64
- только при запуске.
65
- - Проверка права внутри `handle`: право проверяется до тела.
66
- - Своё объявление прав рядом с маршрутами: оно разойдётся с декларацией меню, и получится
67
- «пункта не видно, а страница открывается».
68
- - Гвард, повешенный на защищённую группу целиком: он отрабатывает один раз за загрузку
69
- страницы и переходов между разделами не видит.
70
- - Ожидание прав, которое роняется на отказе запроса: с неизвестными правами не закрывается
71
- ничего, и пустая шапка выхода владельцу не оставляет.
@@ -1,104 +0,0 @@
1
- ---
2
- name: seo-page
3
- kind: pattern
4
- rule: seo
5
- description: Паттерн правила seo. Брать, когда правится разметка страницы публичного сайта, заводится маршрут или страница должна попасть в карту сайта: готовый вызов службы мета-тегов, ветки локалей, строка карты. Проверка отданной разметки — паттерн seo-verify.
6
- ---
7
-
8
- # Разметка страницы публичного сайта
9
-
10
- Паттерн правила `seo`. Что при этом должно быть верно — закон
11
- `docs/constitution/application/search-visibility.md`.
12
-
13
- ## Когда брать
14
-
15
- - Правится шаблон страницы сайта, её заголовок, описание или картинка для соцсетей.
16
- - Заводится новый маршрут сайта.
17
- - Появилась страница, которая должна попасть в `sitemap.xml`.
18
-
19
- ## Разметку ставит сервис, а не шаблон
20
-
21
- Страница собирает данные и одним вызовом отдаёт их `PropertySeoService`. Своих `<meta>` в
22
- шаблоне не заводить: они не помечены `data-<префикс>-seo`, не переписываются при переходе и
23
- переживут смену страницы.
24
-
25
- Образец — `property-page.component.ts:#applySeo`:
26
-
27
- ```typescript
28
- readonly #seo: PropertySeoService = inject(PropertySeoService);
29
-
30
- constructor() {
31
- effect((): void => this.#applySeo());
32
- }
33
-
34
- #applySeo(): void {
35
- const property: IProperty.State | null = this.property();
36
- if (!property) {
37
- return;
38
- }
39
-
40
- const name: string = this.propertyName();
41
- const cover: IPhotoView | null = this.coverDesktop();
42
-
43
- this.#seo.apply({
44
- property,
45
- name,
46
- pageTitle: name ? `${name} — ${this.#titleSuffix()}` : this.#titleSuffix(),
47
- description: property.shortDescription || this.#descriptionFallback(),
48
- ogImageUrl: cover ? photoOgUrl(cover.baseUrl) : `${SITE_ORIGIN}/${BRAND_OG_IMAGE}`,
49
- ogImageAlt: cover ? cover.alt : name,
50
- ogImageWidth: OG_IMAGE_WIDTH,
51
- ogImageHeight: OG_IMAGE_HEIGHT,
52
- locale: this.#localeId,
53
- });
54
- }
55
- ```
56
-
57
- Вызов идёт из `effect`, а не из конструктора напрямую: объект приходит сигналом, и на первом
58
- кадре его ещё нет. Ранний выход по пустому объекту обязателен — без него разметка встала бы на
59
- пустых значениях и второй раз уже не переписалась бы.
60
-
61
- У картинки есть запасной вариант: объект без обложки отдаёт брендовое изображение того же формата,
62
- иначе превью в соцсети пустует.
63
-
64
- ## Новый маршрут заводится веткой на каждую локаль
65
-
66
- Ветки собирает `apps/site/src/app/app.routes.ts` из `LOCALE_CODES`. Локаль по умолчанию своей
67
- ветки не имеет — она отдаётся из корня.
68
-
69
- ```typescript
70
- export const appRoutes: Route[] = [
71
- ...LOCALE_CODES.filter((code: ELocale): boolean => code !== DEFAULT_LOCALE).map((code: ELocale): Route => ({
72
- path: code,
73
- children: propertyRoutes,
74
- })),
75
- ...propertyRoutes,
76
- ];
77
- ```
78
-
79
- Новый путь дописывается в `propertyRoutes` — тогда он появляется во всех восьми ветках сразу.
80
- Ветки перечислены явными кодами, а не `:locale`: иначе `/<адрес страницы>` был бы принят за
81
- язык, а не за адрес объекта.
82
-
83
- ## Страница попадает в карту сайта явно
84
-
85
- Карта строится из объектов, а не из маршрутов, и новая страница сама туда не попадёт. Записи
86
- собирает `buildSitemap` (слой `util` домена страницы объекта), отдаёт обработчик
87
- `/sitemap.xml` в `apps/site/src/server.ts`. Правка идёт вместе со спекой в
88
- `sitemap.util.spec.ts`.
89
-
90
- ## Тексты идут из словарей
91
-
92
- `pageTitle` и `description` собираются из переведённых значений и заводятся во всех восьми
93
- локалях. Без перевода они уезжают в выдачу по-английски, и заметно это только в чужой локали.
94
-
95
- ## Частые промахи
96
-
97
- - `<meta>` в шаблоне вместо вызова сервиса — тег не помечен `data-<префикс>-seo` и переживёт переход.
98
- - Вызов без раннего выхода по пустому объекту — разметка встаёт на пустых значениях.
99
- - Новый путь дописан мимо `propertyRoutes` — работает только в локали по умолчанию,
100
- `/de/<путь>` отдаёт 404.
101
- - Свой разбор адреса вместо `splitRequestUrl` — теряется всё после второго `?`, и источник
102
- заявки считается неверно.
103
- - Правка разметки без проверки на прод-сборке: в дев-сервере теги ставит не тот путь. Проверка
104
- описана паттерном `seo-verify`.
@@ -1,83 +0,0 @@
1
- ---
2
- name: seo-verify
3
- kind: pattern
4
- rule: seo
5
- description: Паттерн правила seo. Брать после любой правки, задевающей разметку публичного сайта, meta или маршруты — готовые команды сборки, поднятия SSR и проверки отданного HTML по локалям, карты сайта и кэшируемости перенаправления. Не брать для самой правки разметки — это паттерн seo-page.
6
- ---
7
-
8
- # Проверка разметки на прод-сборке
9
-
10
- Паттерн правила `seo`. Что при этом должно быть верно — закон
11
- `docs/constitution/application/search-visibility.md`.
12
-
13
- ## Когда брать
14
-
15
- После любой правки, задевающей разметку сайта, `meta`, маршруты, `server.ts`, `robots.txt`
16
- или `deploy/nginx.conf`.
17
-
18
- ## Дев-сервер здесь не показатель
19
-
20
- Теги ставятся при отдаче страницы сервером. В дев-режиме этот путь другой, поэтому проверка
21
- идёт на собранном приложении с поднятым SSR.
22
-
23
- ```bash
24
- npx nx build site
25
- PORT={{prodSitePort}} node dist/apps/site/server/server.mjs &
26
- ```
27
-
28
- Порт {{prodSitePort}} — тот же, что берёт стенд; дев-серверы владельца на {{sitePort}} и {{adminPort}} при этом не
29
- трогаются. Angular SSR отвечает `400` на чужой `Host`, поэтому запросы идут с
30
- `-H "Host: localhost"`.
31
-
32
- ## Что смотреть в отданном HTML
33
-
34
- ```bash
35
- for locale in "" <префиксы локалей>; do
36
- printf '%-10s ' "${locale:-en}"
37
- curl -s -H "Host: localhost" "http://localhost:{{prodSitePort}}/${locale}<адрес страницы>" \
38
- | grep -c -E '<title>|name="description"|property="og:|rel="canonical"|hreflang=|application/ld\+json'
39
- done
40
- ```
41
-
42
- В ответе каждой локали должны быть `<title>`, `description`, набор `og:*`, `canonical`,
43
- полный набор `hreflang` плюс `x-default` и блок `application/ld+json`.
44
-
45
- Отдельно проверяется, что `canonical` ведёт на **свой** язык, а не на локаль по умолчанию:
46
-
47
- ```bash
48
- curl -s -H "Host: localhost" http://localhost:{{prodSitePort}}/de/<адрес страницы> \
49
- | grep -o 'rel="canonical" href="[^"]*"'
50
- ```
51
-
52
- ## Карта сайта
53
-
54
- ```bash
55
- curl -s -H "Host: localhost" http://localhost:{{prodSitePort}}/sitemap.xml | head -20
56
- ```
57
-
58
- Карта строится из живых данных, а не из файла. Пустой ответ означает, что не поднялся запрос
59
- за объектами, — это отказ, а не «объектов нет».
60
-
61
- ## Кэшируемость перенаправления
62
-
63
- Проверяется только на стенде с настоящим `deploy/nginx.conf`: голый SSR отдаёт заголовки, но
64
- не показывает, попадёт ли ответ в кэш.
65
-
66
- ```bash
67
- curl -sI http://<стенд>/<прежний-slug> | grep -i -E 'HTTP/|location|cache-control|x-cache'
68
- ```
69
-
70
- Ответ обязан быть `301`, нести `Cache-Control` со сроком и попадать в кэш. Некэшируемое
71
- перенаправление не вытесняет старую запись, и гость видит прежнюю страницу до суток.
72
-
73
- ## Тексты
74
-
75
- `title` и `description` берутся из словарей во всех локалях перевода. Непереведённый ключ уезжает
76
- в выдачу по-английски — полноту словарей держит `npx nx test common-i18n`.
77
-
78
- ## Частые промахи
79
-
80
- - Проверка в дев-сервере: разметку там ставит другой путь, и отсутствие тега не видно.
81
- - Проверка одной локали: расходится обычно та, которую не смотрели.
82
- - Проверка кэша без nginx: заголовки видны, попадание в кэш — нет.
83
- - Вывод по коду ответа: `200` приходит и со страницы без разметки.