@rt-tools/agent-kit 0.8.1 → 0.8.2

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 +24 -19
  2. package/assets/agents/qa-engineer.md +1 -1
  3. package/assets/checks/board.github.mjs +56 -17
  4. package/assets/checks/check-board.github.mjs +49 -5
  5. package/assets/checks/task-new.github.mjs +33 -5
  6. package/assets/commands/agent-kit-digest.md +10 -5
  7. package/assets/commands/next-session.md +4 -4
  8. package/assets/commands/skill-curator.md +11 -9
  9. package/assets/defaults/gate-map.sh +11 -4
  10. package/assets/defaults/project.sh +46 -0
  11. package/assets/docs/GLOSSARY.md +28 -26
  12. package/assets/hooks/docs-guard.sh +19 -3
  13. package/assets/hooks/git-guard-delivery.sh +106 -13
  14. package/assets/hooks/proposal-guard.sh +93 -0
  15. package/assets/hooks/reuse-first-guard.sh +68 -14
  16. package/assets/hooks/skill-gate-layers.sh +1 -1
  17. package/assets/hooks/skill-gate.sh +26 -0
  18. package/assets/hooks/task-flow-guard.sh +44 -18
  19. package/assets/hooks/window-fill-guard.sh +1 -1
  20. package/assets/laws/delivery.md +13 -7
  21. package/assets/laws/project-documentation.md +21 -0
  22. package/assets/laws/verifiability.md +6 -1
  23. package/assets/laws/work-conduct.md +67 -3
  24. package/assets/patterns/git-workflow-commit.azure.md +10 -2
  25. package/assets/patterns/git-workflow-commit.github.md +15 -2
  26. package/assets/patterns/git-workflow-commit.gitlab.md +10 -2
  27. package/assets/patterns/git-workflow-merge.md +1 -1
  28. package/assets/patterns/spec-driven-domain.md +7 -1
  29. package/assets/patterns/spec-driven-rule.md +6 -0
  30. package/assets/patterns/task-flow-close.md +78 -9
  31. package/assets/patterns/task-flow-handoff.md +27 -4
  32. package/assets/patterns/task-flow-resume.md +23 -5
  33. package/assets/patterns/task-flow-start.md +5 -1
  34. package/assets/rules/browser-verification.md +10 -1
  35. package/assets/rules/doc-style.md +13 -5
  36. package/assets/rules/git-workflow.azure.md +12 -7
  37. package/assets/rules/git-workflow.github.md +31 -13
  38. package/assets/rules/git-workflow.gitlab.md +12 -7
  39. package/assets/rules/spec-driven.md +4 -0
  40. package/assets/rules/task-flow.md +47 -22
  41. package/assets/rules/testing.md +19 -0
  42. package/assets/rules/typescript-conventions.md +12 -0
  43. package/assets/skills/agent-kit-extend.md +24 -0
  44. package/assets/skills/agent-kit.md +54 -6
  45. package/assets/traits.json +14 -0
  46. package/bin/agent-kit.d.ts.map +1 -1
  47. package/bin/agent-kit.js +31 -16
  48. package/bin/agent-kit.js.map +1 -1
  49. package/index.d.ts +1 -0
  50. package/index.d.ts.map +1 -1
  51. package/index.js +1 -0
  52. package/index.js.map +1 -1
  53. package/lib/argv.d.ts +17 -0
  54. package/lib/argv.d.ts.map +1 -0
  55. package/lib/argv.js +44 -0
  56. package/lib/argv.js.map +1 -0
  57. package/lib/cargo.d.ts +88 -0
  58. package/lib/cargo.d.ts.map +1 -0
  59. package/lib/cargo.js +16 -0
  60. package/lib/cargo.js.map +1 -0
  61. package/lib/catalog.d.ts +18 -1
  62. package/lib/catalog.d.ts.map +1 -1
  63. package/lib/catalog.js +12 -2
  64. package/lib/catalog.js.map +1 -1
  65. package/lib/commands.d.ts +0 -26
  66. package/lib/commands.d.ts.map +1 -1
  67. package/lib/commands.js +78 -122
  68. package/lib/commands.js.map +1 -1
  69. package/lib/companion.d.ts +37 -0
  70. package/lib/companion.d.ts.map +1 -1
  71. package/lib/companion.js +42 -1
  72. package/lib/companion.js.map +1 -1
  73. package/lib/config.d.ts +28 -0
  74. package/lib/config.d.ts.map +1 -1
  75. package/lib/config.js +20 -0
  76. package/lib/config.js.map +1 -1
  77. package/lib/ship.d.ts +39 -0
  78. package/lib/ship.d.ts.map +1 -0
  79. package/lib/ship.js +87 -0
  80. package/lib/ship.js.map +1 -0
  81. package/lib/shipment.d.ts +60 -0
  82. package/lib/shipment.d.ts.map +1 -0
  83. package/lib/shipment.js +247 -0
  84. package/lib/shipment.js.map +1 -0
  85. package/lib/snapshot.d.ts +30 -0
  86. package/lib/snapshot.d.ts.map +1 -0
  87. package/lib/snapshot.js +73 -0
  88. package/lib/snapshot.js.map +1 -0
  89. package/lib/traits.d.ts +32 -0
  90. package/lib/traits.d.ts.map +1 -0
  91. package/lib/traits.js +82 -0
  92. package/lib/traits.js.map +1 -0
  93. package/package.json +6 -2
  94. package/rt-tools-agent-kit-0.8.2.tgz +0 -0
  95. package/lib/submit.d.ts +0 -24
  96. package/lib/submit.d.ts.map +0 -1
  97. package/lib/submit.js +0 -26
  98. package/lib/submit.js.map +0 -1
  99. package/rt-tools-agent-kit-0.8.1.tgz +0 -0
  100. /package/assets/rules/{entity-conventions.md → entity-conventions.needs-admin.md} +0 -0
  101. /package/assets/rules/{observability.md → observability.needs-app.md} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/catalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Что пакет везёт — прочитанное с диска, до всякого выбора проекта.\n *\n * Каталог нужен отдельно от раскладки, потому что выбирать приходится раньше, чем раскладывать:\n * `init` спрашивает, какие законы взять, а `list` показывает и то, от чего проект отказался.\n * И там и там нужен полный набор, а `collectAssets` по устройству отдаёт уже отобранный.\n */\nimport { accessSync, constants, Dirent, readdirSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { KINDS, TKind } from './config.js';\nimport { IAxis, IVariant, matchesVariant, readAxes, variantOf, withoutVariant } from './variants.js';\n\nexport interface IEntryOfCatalog {\n /** Идентификатор: `laws/application/money.md`. Он же путь надстройки и ключ в `only` и `skip`. */\n readonly id: string;\n readonly kind: TKind;\n /**\n * Имя без рода, вида и расширения: `delivery`, `application/money`. Им ресурс называют в\n * строке запуска, и им же он ложится в дерево проекта.\n */\n readonly name: string;\n /** Заголовок первой строки файла; у файла без заголовка — его имя. */\n readonly title: string;\n /** Вид ресурса, если он лежит в пакете в нескольких: `git-workflow.github.md`. */\n readonly variant: IVariant | null;\n readonly text: string;\n /**\n * Запускается ли файл сам по себе. Гард зовут по пути, а не через оболочку, и файл без\n * права на запуск отвечает отказом доступа — то есть ненулевым кодом, который читается как\n * «правка отбита». Признак берётся с файла в пакете и переносится на разложенный.\n */\n readonly executable: boolean;\n /**\n * Ресурсы, без которых этот неисполним, — их идентификаторы.\n *\n * Читается из шапки самого ресурса, а не из отдельного списка при пакете: список разошёлся\n * бы с ресурсами молча, ровно тем же молчанием, ради которого требование и заведено. Паттерн\n * возвращения к работе начинается словами «хук запуска сессии отдал замысел и ход работы» —\n * без хука он неисполним, и до этой строки узнать об этом было нечем.\n */\n readonly requires: readonly string[];\n}\n\nconst TITLE: RegExp = /^#\\s+(\\S.*)$/m;\n\n/** Что объявила шапка ресурса. Пустое поле и отсутствующее здесь одно и то же. */\nexport interface IFrontMatter {\n readonly name: string;\n readonly kind: string;\n /** Закон, под которым стоит правило. */\n readonly law: string;\n /** Правило, при котором стоит паттерн. */\n readonly rule: string;\n}\n\nconst FIELD: RegExp = /^([a-z]+):\\s*(\\S.*?)\\s*$/;\n\n/**\n * Вступление между `---` в начале файла; его нет — вернётся пустая шапка.\n *\n * Живёт при каталоге, потому что связь родителя с потомком читают двое: сверка связности пакета\n * и каскад отказа. Объявление у одного из них заводило бы круговой импорт между ними.\n */\nexport function frontMatterOf(text: string): IFrontMatter {\n const lines: readonly string[] = text.split('\\n');\n const found: Record<string, string> = {};\n\n if (lines[0]?.trim() === '---') {\n for (const line of lines.slice(1)) {\n if (line.trim() === '---') {\n break;\n }\n const match: RegExpMatchArray | null = line.match(FIELD);\n if (match) {\n found[match[1]] = match[2];\n }\n }\n }\n\n return { name: found['name'] ?? '', kind: found['kind'] ?? '', law: found['law'] ?? '', rule: found['rule'] ?? '' };\n}\n\n/**\n * Строка требования в шапке ресурса. Двух видов, потому что ресурсы двух родов: у разметки\n * `**Требует:**`, у исполняемого файла — комментарий `# Требует:`.\n */\nconst REQUIRES: RegExp = /^(?:#\\s*Требует:|\\*\\*Требует:\\*\\*)\\s*(.+)$/m;\n\n/**\n * Что ресурс о себе объявил. Имена читаются идентификаторами — `hooks/observe.sh`: короткая\n * форма совпала бы у закона и правила с одним именем, а требование, указавшее не туда, хуже\n * ненайденного.\n */\nexport function requiresOf(text: string): readonly string[] {\n const found: RegExpMatchArray | null = text.match(REQUIRES);\n if (!found) {\n return [];\n }\n\n return found[1]\n .split(',')\n .map((name: string): string => name.trim().replace(/^`|`$/g, '').trim())\n .filter((name: string): boolean => name.length > 0);\n}\n\n/** Запускается ли файл сам по себе. Спрашиваем систему, а не разбираем биты режима руками. */\nfunction isExecutable(path: string): boolean {\n try {\n accessSync(path, constants.X_OK);\n\n return true;\n } catch {\n return false;\n }\n}\n\n/** Заголовок нужен человеку, который выбирает: имена законов ему ни о чём не говорят. */\nexport function titleOf(text: string, fallback: string): string {\n const found: RegExpMatchArray | null = text.match(TITLE);\n\n return found ? found[1].trim() : fallback;\n}\n\n/**\n * Файлы рода — вместе с теми, что лежат в подкаталогах.\n *\n * Обход рекурсивный потому, что у законов два слоя: общий закон верен любому приложению этого\n * класса, а закон приложения — только такому, где есть деньги, локали или владеющая сущность.\n * Слой отражён каталогом, и путь внутри рода едет в идентификатор целиком, чтобы разложенное\n * повторяло раскладку пакета.\n */\nfunction filesOf(dir: string, prefix: string = ''): readonly string[] {\n let entries: readonly Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const files: string[] = [];\n for (const entry of [...entries].sort((one: Dirent, two: Dirent): number => one.name.localeCompare(two.name))) {\n if (entry.isFile()) {\n files.push(`${prefix}${entry.name}`);\n } else if (entry.isDirectory()) {\n files.push(...filesOf(join(dir, entry.name), `${prefix}${entry.name}/`));\n }\n }\n\n return files;\n}\n\n/**\n * Всё, что лежит в ресурсах пакета. Список не выписан руками, а читается из каталога:\n * выписанный разошёлся бы с ним на первом же добавленном законе, и заметить это было бы\n * нечем — ресурс просто не приезжал бы к проекту.\n */\nexport function readCatalog(assetsDir: string): readonly IEntryOfCatalog[] {\n const axes: readonly IAxis[] = readAxes(assetsDir);\n const entries: IEntryOfCatalog[] = [];\n\n for (const kind of KINDS) {\n for (const file of filesOf(join(assetsDir, kind))) {\n const path: string = join(assetsDir, kind, file);\n const text: string = readFileSync(path, 'utf8');\n const variant: IVariant | null = variantOf(file, axes);\n const name: string = withoutVariant(file, variant).replace(/\\.[^./]+$/, '');\n const executable: boolean = isExecutable(path);\n entries.push({\n id: `${kind}/${file}`,\n kind,\n name,\n title: titleOf(text, name),\n variant,\n text,\n executable,\n requires: requiresOf(text),\n });\n }\n }\n\n return entries;\n}\n\n/** Чем проект ограничил раскладку: выбором, отказом и видами, которые он назвал. */\nexport interface ISelection {\n readonly only: readonly string[];\n readonly skip: readonly string[];\n readonly variants: Readonly<Record<string, string>>;\n}\n\n/**\n * Взят ли ресурс при этом выборе.\n *\n * `only` ограничивает **только те роды, которые сам называет**. Проект, выбравший девять\n * законов из пятнадцати, шаблоны при этом не терял бы: перечисляя законы, он говорит о\n * законах, а не обо всём, что пакет везёт. Обратное правило читалось бы как «выбрал законы —\n * остался без шаблонов», и заметил бы это только тот, кто пошёл за шаблоном.\n *\n * `skip` вычитает из выбранного. Отказ от одного закона не должен требовать переписать\n * список из пятнадцати, а точечное исключение остаётся точечным.\n *\n * Вид отбирается раньше обоих: ресурс чужого хостинга не «не выбран» — его в этом дереве не\n * существует вовсе, и называть его в `skip` проекту незачем.\n */\nexport function isChosen(entry: IEntryOfCatalog, selection: ISelection): boolean {\n if (!matchesVariant(entry.variant, selection.variants)) {\n return false;\n }\n if (selection.skip.includes(entry.id)) {\n return false;\n }\n const restricted: boolean = selection.only.some((id: string): boolean => id.startsWith(`${entry.kind}/`));\n\n return !restricted || selection.only.includes(entry.id);\n}\n\n/** Ресурс, у которого виды есть, но ни один не отвечает тому, что выбрало дерево. */\nexport interface IGapOfVariant {\n readonly kind: TKind;\n /** Имя без вида — под ним ресурс лёг бы в дерево. */\n readonly name: string;\n readonly axis: string;\n /** Что выбрало дерево по этой оси. */\n readonly chosen: string;\n /** Виды, которые у ресурса есть на самом деле. */\n readonly available: readonly string[];\n /** Идентификаторы этих видов: ими же пробел и снимается через `skip`. */\n readonly ids: readonly string[];\n}\n\n/**\n * Ресурсы, оставшиеся без вида под выбор дерева.\n *\n * Молчаливый пропуск такого ресурса — худший из исходов. Правило поставки едет тремя видами, а\n * команды заведения задачи и сверки очереди — одним: дерево, выбравшее чужой хостинг, получает\n * правило, которое зовёт три команды, и ни одного скрипта под ними. Отказа при этом не бывает —\n * ресурс просто не приезжает, и узнают об этом, когда команда из правила не находится.\n *\n * Отсюда правило: **ресурс с видами, ни один из которых не совпал, — отказ, а не пропуск.**\n * Снимается он двумя способами, и оба явные: завести недостающий вид в пакете либо назвать\n * существующие в `skip` — дерево тем самым говорит, что обходится без этого ресурса.\n */\nexport function variantGaps(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IGapOfVariant[] {\n const groups: Map<string, IEntryOfCatalog[]> = new Map();\n for (const entry of catalog) {\n if (entry.variant === null) {\n continue;\n }\n const key: string = `${entry.kind}/${entry.name}`;\n groups.set(key, [...(groups.get(key) ?? []), entry]);\n }\n\n const gaps: IGapOfVariant[] = [];\n for (const entries of groups.values()) {\n const axis: string = entries[0].variant?.axis ?? '';\n if (entries.some((entry: IEntryOfCatalog): boolean => matchesVariant(entry.variant, selection.variants))) {\n continue;\n }\n // Отказ от ресурса — законный ответ, и повторять его отказом раскладки незачем: дерево\n // уже сказало, что обходится без него.\n if (entries.every((entry: IEntryOfCatalog): boolean => selection.skip.includes(entry.id))) {\n continue;\n }\n // Род, суженный через `only`, отбирает ресурсы поимённо: не названный в нём ресурс не\n // пропал — его не просили.\n const restricted: boolean = selection.only.some((id: string): boolean => id.startsWith(`${entries[0].kind}/`));\n if (restricted && !entries.some((entry: IEntryOfCatalog): boolean => selection.only.includes(entry.id))) {\n continue;\n }\n gaps.push({\n kind: entries[0].kind,\n name: entries[0].name,\n axis,\n chosen: selection.variants[axis] ?? '',\n available: entries.map((entry: IEntryOfCatalog): string => entry.variant?.value ?? ''),\n ids: entries.map((entry: IEntryOfCatalog): string => entry.id),\n });\n }\n\n return gaps;\n}\n\n/** Ресурс, снятый вслед за отвергнутым родителем. */\nexport interface ICascadeCut {\n readonly id: string;\n /** Ближайший родитель: у правила — его закон, у паттерна — его правило. */\n readonly parent: string;\n /**\n * Отвергнутый корень цепочки — то, что дерево действительно отвергло или не выбрало.\n * Ближайшего родителя мало: паттерн уходит вслед за правилом, которого в отказе нет, и\n * искать его там читатель пойдёт зря.\n */\n readonly root: string;\n}\n\n/** Последнее звено имени: `application/money` → `money`. Им ресурсы и ссылаются друг на друга. */\nconst shortNameOf: (entry: IEntryOfCatalog) => string = (entry: IEntryOfCatalog): string => entry.name.split('/').pop() ?? entry.name;\n\n/**\n * Ресурсы, снятые каскадом: правила при невзятом законе и паттерны при невзятых правилах.\n *\n * Связь читается из вступления самого ресурса — отдельный список при пакете разошёлся бы с\n * ресурсами молча, тем же молчанием, ради которого каскад и заводится. Родитель ищется по\n * последнему звену имени: полного пути в шапке нет, потому что переезд закона между слоями\n * переписывал бы шапки всех правил при нём.\n *\n * Родителя в каталоге нет вовсе — каскад молчит: снимать не по чему, а промах в шапке судит\n * сверка связности пакета, до всякой раскладки в дереве.\n */\nexport function cascadeCuts(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly ICascadeCut[] {\n const takenOf: (kind: TKind) => ReadonlySet<string> = (kind: TKind): ReadonlySet<string> =>\n new Set(catalog.filter((entry: IEntryOfCatalog): boolean => entry.kind === kind && isChosen(entry, selection)).map(shortNameOf));\n const knownOf: (kind: TKind) => ReadonlySet<string> = (kind: TKind): ReadonlySet<string> =>\n new Set(catalog.filter((entry: IEntryOfCatalog): boolean => entry.kind === kind).map(shortNameOf));\n\n const takenLaws: ReadonlySet<string> = takenOf('laws');\n const knownLaws: ReadonlySet<string> = knownOf('laws');\n const takenRules: ReadonlySet<string> = takenOf('rules');\n const knownRules: ReadonlySet<string> = knownOf('rules');\n\n const cuts: ICascadeCut[] = [];\n /** Правило, снятое законом: для паттернов при нём корнем цепочки будет этот закон. */\n const rootByRule: Map<string, string> = new Map<string, string>();\n\n for (const entry of catalog) {\n if (entry.kind !== 'rules' || !isChosen(entry, selection)) {\n continue;\n }\n const law: string = frontMatterOf(entry.text).law;\n if (!law || !knownLaws.has(law) || takenLaws.has(law)) {\n continue;\n }\n cuts.push({ id: entry.id, parent: law, root: law });\n rootByRule.set(shortNameOf(entry), law);\n }\n\n for (const entry of catalog) {\n if (entry.kind !== 'patterns' || !isChosen(entry, selection)) {\n continue;\n }\n const rule: string = frontMatterOf(entry.text).rule;\n if (!rule || !knownRules.has(rule) || (takenRules.has(rule) && !rootByRule.has(rule))) {\n continue;\n }\n cuts.push({ id: entry.id, parent: rule, root: rootByRule.get(rule) ?? rule });\n }\n\n return cuts;\n}\n\n/**\n * Ресурсы, названные выбором поимённо и всё равно снятые каскадом.\n *\n * Дерево, назвавшее правило в выборе при невыбранном законе, не получает его молча — то самое\n * молчание, ради которого каскад и заводится, только с другой стороны: там о снятии не знал тот,\n * кто ничего не просил, здесь — тот, кто попросил прямо.\n */\nexport function namedButCut(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly ICascadeCut[] {\n return cascadeCuts(catalog, selection).filter((one: ICascadeCut): boolean => selection.only.includes(one.id));\n}\n\n/**\n * Что ложится в дерево: прямой отбор за вычетом снятого каскадом.\n *\n * Отдельной функцией, а не внутри `isChosen`, потому что каскад знает весь каталог, а отбор —\n * один ресурс: связь родителя с потомком по одной записи не видна.\n */\nexport function chosenEntries(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IEntryOfCatalog[] {\n const cut: ReadonlySet<string> = new Set(cascadeCuts(catalog, selection).map((one: ICascadeCut): string => one.id));\n\n return catalog.filter((entry: IEntryOfCatalog): boolean => isChosen(entry, selection) && !cut.has(entry.id));\n}\n\n/** Строка отказа, которая ничего не снимает. */\nexport interface IIdleSkip {\n readonly id: string;\n /** Чем названное ею уже снято: имя отвергнутого родителя, либо пусто — такого ресурса нет. */\n readonly by: string;\n}\n\n/**\n * Строки отказа, которые ничего не снимают: названное ими снято каскадом или в каталоге не\n * значится вовсе.\n *\n * Не отказ, а предупреждение: настройка, верная до обновления пакета, становится лишней сама,\n * без единой правки в дереве, и отбивать за это раскладку значило бы наказывать за вчерашнюю\n * правоту.\n *\n * Строка на ресурс чужого вида лишней не считается никогда: ею дерево гасит отказ о ресурсе,\n * у которого нет подходящего вида, — то есть работу она делает.\n */\nexport function idleSkips(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IIdleSkip[] {\n const idle: IIdleSkip[] = [];\n\n for (const id of selection.skip) {\n const entry: IEntryOfCatalog | undefined = catalog.find((one: IEntryOfCatalog): boolean => one.id === id);\n if (!entry) {\n idle.push({ id, by: '' });\n continue;\n }\n if (!matchesVariant(entry.variant, selection.variants)) {\n continue;\n }\n // Каскад считается от отбора без этой строки: иначе снятое ею самой читалось бы как\n // снятое родителем, и лишней оказалась бы любая строка отказа подряд.\n const without: ISelection = { ...selection, skip: selection.skip.filter((one: string): boolean => one !== id) };\n const cut: ICascadeCut | undefined = cascadeCuts(catalog, without).find((one: ICascadeCut): boolean => one.id === id);\n if (cut) {\n idle.push({ id, by: cut.root });\n }\n }\n\n return idle;\n}\n\n/** Выбранный ресурс, чьё требование в дерево не поехало. */\nexport interface IBrokenLink {\n /** Кто требует — идентификатор выбранного ресурса. */\n readonly id: string;\n /** Чего не хватает — идентификатор требования. */\n readonly requires: string;\n /** Требования нет в пакете вовсе: промах в шапке ресурса, а не выбор дерева. */\n readonly unknown: boolean;\n}\n\n/**\n * Связи, разорванные выбором дерева.\n *\n * Не отказ, а предупреждение — и это главное свойство. Дерево вправе закрыть требование своим\n * средством: паттерн исполним и с чужим хуком, если тот делает то же самое. Отказ здесь отбивал\n * бы законную раскладку, а молчание оставляет дерево исправным на вид — сверка зелена на любом\n * подмножестве, сколько бы связок ни было разорвано.\n */\nexport function brokenLinks(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IBrokenLink[] {\n const known: ReadonlySet<string> = new Set(catalog.map((entry: IEntryOfCatalog): string => entry.id));\n const chosen: ReadonlySet<string> = new Set(\n catalog.filter((entry: IEntryOfCatalog): boolean => isChosen(entry, selection)).map((entry: IEntryOfCatalog): string => entry.id)\n );\n\n const broken: IBrokenLink[] = [];\n for (const entry of catalog) {\n if (!chosen.has(entry.id)) {\n continue;\n }\n for (const required of entry.requires) {\n if (chosen.has(required)) {\n continue;\n }\n broken.push({ id: entry.id, requires: required, unknown: !known.has(required) });\n }\n }\n\n return broken;\n}\n\n/**\n * Идентификатор по тому, как ресурс назвали в строке запуска. Принимаются все формы, которыми\n * его называют вслух: `money`, `application/money`, `money.md` и `laws/application/money.md`.\n */\nexport function idOf(spoken: string, kind: TKind, catalog: readonly IEntryOfCatalog[]): string | null {\n const wanted: string = spoken.trim();\n const shortOf: (entry: IEntryOfCatalog) => string = (entry: IEntryOfCatalog): string => entry.name.split('/').pop() ?? entry.name;\n const found: IEntryOfCatalog | undefined = catalog.find(\n (entry: IEntryOfCatalog): boolean =>\n entry.kind === kind &&\n (entry.id === wanted || entry.name === wanted || shortOf(entry) === wanted || entry.id.endsWith(`/${wanted}`))\n );\n\n return found ? found.id : null;\n}\n\nexport interface IResolvedSelection {\n readonly ids: readonly string[];\n /** Имена, которым в каталоге ничего не отвечает: промах в строке запуска, а не выбор. */\n readonly unknown: readonly string[];\n}\n\n/** Разбор `--laws access,delivery`: имя без ответа — промах, и молчать о нём нельзя. */\nexport function resolveSelection(spoken: readonly string[], kind: TKind, catalog: readonly IEntryOfCatalog[]): IResolvedSelection {\n const ids: string[] = [];\n const unknown: string[] = [];\n\n for (const name of spoken) {\n const id: string | null = idOf(name, kind, catalog);\n if (id === null) {\n unknown.push(name.trim());\n } else if (!ids.includes(id)) {\n ids.push(id);\n }\n }\n\n return { ids, unknown };\n}\n"]}
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAU,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,KAAK,EAAS,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAmB,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAyCrG,MAAM,KAAK,GAAW,eAAe,CAAC;AAYtC,MAAM,KAAK,GAAW,0BAA0B,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACtC,MAAM,KAAK,GAAsB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,KAAK,GAA2B,EAAE,CAAC;IAEzC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,KAAK,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAChC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,EAAE,CAAC;gBACxB,MAAM;YACV,CAAC;YACD,MAAM,KAAK,GAA4B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACzD,IAAI,KAAK,EAAE,CAAC;gBACR,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAC/B,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;AACxH,CAAC;AAED;;;GAGG;AACH,MAAM,QAAQ,GAAW,6CAA6C,CAAC;AAEvE;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACnC,MAAM,KAAK,GAA4B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC5D,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC,CAAC,CAAC;SACV,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;SACvE,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,8FAA8F;AAC9F,SAAS,YAAY,CAAC,IAAY;IAC9B,IAAI,CAAC;QACD,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAEjC,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,QAAgB;IAClD,MAAM,KAAK,GAA4B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAEzD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC9C,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,OAAO,CAAC,GAAW,EAAE,SAAiB,EAAE;IAC7C,IAAI,OAA0B,CAAC;IAC/B,IAAI,CAAC;QACD,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,GAAW,EAAE,GAAW,EAAU,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC5G,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACjB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;aAAM,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAC7E,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiB;IACzC,MAAM,IAAI,GAAqB,QAAQ,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,OAAO,GAAsB,EAAE,CAAC;IAEtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,GAAW,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YACjD,MAAM,IAAI,GAAW,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAChD,MAAM,OAAO,GAAoB,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACvD,MAAM,KAAK,GAAkB,aAAa,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;YAC1E,MAAM,IAAI,GAAW,kBAAkB,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YACvG,MAAM,UAAU,GAAY,YAAY,CAAC,IAAI,CAAC,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC;gBACT,EAAE,EAAE,GAAG,IAAI,IAAI,IAAI,EAAE;gBACrB,IAAI;gBACJ,IAAI;gBACJ,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;gBAC1B,OAAO;gBACP,IAAI;gBACJ,KAAK;gBACL,UAAU;gBACV,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC;aAC7B,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AAcD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAsB,EAAE,SAAqB;IAClE,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACpC,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,MAAM,UAAU,GAAY,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAU,EAAW,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IAC1G,IAAI,UAAU,EAAE,CAAC;QACb,OAAO,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,kBAAkB,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC;AAgBD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAC,OAAmC,EAAE,SAAqB;IAClF,MAAM,MAAM,GAAmC,IAAI,GAAG,EAAE,CAAC;IACzD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YACzB,SAAS;QACb,CAAC;QACD,MAAM,GAAG,GAAW,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QAClD,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,MAAM,IAAI,GAAoB,EAAE,CAAC;IACjC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,GAAW,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC;QACpD,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACvG,SAAS;QACb,CAAC;QACD,uFAAuF;QACvF,uCAAuC;QACvC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACxF,SAAS;QACb,CAAC;QACD,sFAAsF;QACtF,2BAA2B;QAC3B,MAAM,UAAU,GAAY,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAU,EAAW,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAC/G,IAAI,UAAU,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACtG,SAAS;QACb,CAAC;QACD,IAAI,CAAC,IAAI,CAAC;YACN,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI;YACrB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI;YACrB,IAAI;YACJ,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE;YACtC,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAsB,EAAU,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC;YACtF,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAsB,EAAU,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;SACjE,CAAC,CAAC;IACP,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAeD,kGAAkG;AAClG,MAAM,WAAW,GAAuC,CAAC,KAAsB,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC;AAEtI;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,OAAmC,EAAE,SAAqB;IAClF,MAAM,OAAO,GAAyC,CAAC,IAAW,EAAuB,EAAE,CACvF,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;IACrI,MAAM,OAAO,GAAyC,CAAC,IAAW,EAAuB,EAAE,CACvF,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;IAEvG,MAAM,SAAS,GAAwB,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,MAAM,SAAS,GAAwB,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,MAAM,UAAU,GAAwB,OAAO,CAAC,OAAO,CAAC,CAAC;IACzD,MAAM,UAAU,GAAwB,OAAO,CAAC,OAAO,CAAC,CAAC;IAEzD,MAAM,IAAI,GAAkB,EAAE,CAAC;IAC/B,sFAAsF;IACtF,MAAM,UAAU,GAAwB,IAAI,GAAG,EAAkB,CAAC;IAElE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;YACxD,SAAS;QACb,CAAC;QACD,MAAM,GAAG,GAAW,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC;QAClD,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACpD,SAAS;QACb,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACpD,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;YAC3D,SAAS;QACb,CAAC;QACD,MAAM,IAAI,GAAW,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;QACpD,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACpF,SAAS;QACb,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,OAAmC,EAAE,SAAqB;IAClF,OAAO,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,GAAgB,EAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AAClH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAmC,EAAE,SAAqB;IACpF,MAAM,GAAG,GAAwB,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,GAAgB,EAAU,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAEpH,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;AACjH,CAAC;AASD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,OAAmC,EAAE,SAAqB;IAChF,MAAM,IAAI,GAAgB,EAAE,CAAC;IAE7B,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAgC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAoB,EAAW,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1G,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1B,SAAS;QACb,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrD,SAAS;QACb,CAAC;QACD,oFAAoF;QACpF,sEAAsE;QACtE,MAAM,OAAO,GAAe,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAW,EAAW,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,CAAC;QAChH,MAAM,GAAG,GAA4B,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,GAAgB,EAAW,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACtH,IAAI,GAAG,EAAE,CAAC;YACN,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACpC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAYD;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,OAAmC,EAAE,SAAqB;IAClF,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAsB,EAAU,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IACtG,MAAM,MAAM,GAAwB,IAAI,GAAG,CACvC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAsB,EAAU,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CACpI,CAAC;IAEF,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;YACxB,SAAS;QACb,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACpC,IAAI,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACvB,SAAS;YACb,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACrF,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI,CAAC,MAAc,EAAE,IAAW,EAAE,OAAmC;IACjF,MAAM,MAAM,GAAW,MAAM,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,OAAO,GAAuC,CAAC,KAAsB,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC;IAClI,MAAM,KAAK,GAAgC,OAAO,CAAC,IAAI,CACnD,CAAC,KAAsB,EAAW,EAAE,CAChC,KAAK,CAAC,IAAI,KAAK,IAAI;QACnB,CAAC,KAAK,CAAC,EAAE,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CACrH,CAAC;IAEF,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACnC,CAAC;AAQD,wFAAwF;AACxF,MAAM,UAAU,gBAAgB,CAAC,MAAyB,EAAE,IAAW,EAAE,OAAmC;IACxG,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QACxB,MAAM,EAAE,GAAkB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACpD,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9B,CAAC;aAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;IACL,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC","sourcesContent":["/**\n * Что пакет везёт — прочитанное с диска, до всякого выбора проекта.\n *\n * Каталог нужен отдельно от раскладки, потому что выбирать приходится раньше, чем раскладывать:\n * `init` спрашивает, какие законы взять, а `list` показывает и то, от чего проект отказался.\n * И там и там нужен полный набор, а `collectAssets` по устройству отдаёт уже отобранный.\n */\nimport { accessSync, constants, Dirent, readdirSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { KINDS, TKind } from './config.js';\nimport { answersRequirement, requirementOf, withoutRequirement } from './traits.js';\nimport { IAxis, IVariant, matchesVariant, readAxes, variantOf, withoutVariant } from './variants.js';\n\nexport interface IEntryOfCatalog {\n /** Идентификатор: `laws/application/money.md`. Он же путь надстройки и ключ в `only` и `skip`. */\n readonly id: string;\n readonly kind: TKind;\n /**\n * Имя без рода, вида и расширения: `delivery`, `application/money`. Им ресурс называют в\n * строке запуска, и им же он ложится в дерево проекта.\n */\n readonly name: string;\n /** Заголовок первой строки файла; у файла без заголовка — его имя. */\n readonly title: string;\n /** Вид ресурса, если он лежит в пакете в нескольких: `git-workflow.github.md`. */\n readonly variant: IVariant | null;\n /**\n * Свойство дерева, без которого этот ресурс дереву не нужен: `observability.needs-db.md`.\n *\n * От вида отличается вопросом. Вид спрашивает «какой из трёх» и выбирается один; требование\n * спрашивает «есть ли», и у дерева таких свойств сразу несколько. Ресурс без требования —\n * `null`: он верен всякому дереву.\n */\n readonly needs: string | null;\n readonly text: string;\n /**\n * Запускается ли файл сам по себе. Гард зовут по пути, а не через оболочку, и файл без\n * права на запуск отвечает отказом доступа — то есть ненулевым кодом, который читается как\n * «правка отбита». Признак берётся с файла в пакете и переносится на разложенный.\n */\n readonly executable: boolean;\n /**\n * Ресурсы, без которых этот неисполним, — их идентификаторы.\n *\n * Читается из шапки самого ресурса, а не из отдельного списка при пакете: список разошёлся\n * бы с ресурсами молча, ровно тем же молчанием, ради которого требование и заведено. Паттерн\n * возвращения к работе начинается словами «хук запуска сессии отдал замысел и ход работы» —\n * без хука он неисполним, и до этой строки узнать об этом было нечем.\n */\n readonly requires: readonly string[];\n}\n\nconst TITLE: RegExp = /^#\\s+(\\S.*)$/m;\n\n/** Что объявила шапка ресурса. Пустое поле и отсутствующее здесь одно и то же. */\nexport interface IFrontMatter {\n readonly name: string;\n readonly kind: string;\n /** Закон, под которым стоит правило. */\n readonly law: string;\n /** Правило, при котором стоит паттерн. */\n readonly rule: string;\n}\n\nconst FIELD: RegExp = /^([a-z]+):\\s*(\\S.*?)\\s*$/;\n\n/**\n * Вступление между `---` в начале файла; его нет — вернётся пустая шапка.\n *\n * Живёт при каталоге, потому что связь родителя с потомком читают двое: сверка связности пакета\n * и каскад отказа. Объявление у одного из них заводило бы круговой импорт между ними.\n */\nexport function frontMatterOf(text: string): IFrontMatter {\n const lines: readonly string[] = text.split('\\n');\n const found: Record<string, string> = {};\n\n if (lines[0]?.trim() === '---') {\n for (const line of lines.slice(1)) {\n if (line.trim() === '---') {\n break;\n }\n const match: RegExpMatchArray | null = line.match(FIELD);\n if (match) {\n found[match[1]] = match[2];\n }\n }\n }\n\n return { name: found['name'] ?? '', kind: found['kind'] ?? '', law: found['law'] ?? '', rule: found['rule'] ?? '' };\n}\n\n/**\n * Строка требования в шапке ресурса. Двух видов, потому что ресурсы двух родов: у разметки\n * `**Требует:**`, у исполняемого файла — комментарий `# Требует:`.\n */\nconst REQUIRES: RegExp = /^(?:#\\s*Требует:|\\*\\*Требует:\\*\\*)\\s*(.+)$/m;\n\n/**\n * Что ресурс о себе объявил. Имена читаются идентификаторами — `hooks/observe.sh`: короткая\n * форма совпала бы у закона и правила с одним именем, а требование, указавшее не туда, хуже\n * ненайденного.\n */\nexport function requiresOf(text: string): readonly string[] {\n const found: RegExpMatchArray | null = text.match(REQUIRES);\n if (!found) {\n return [];\n }\n\n return found[1]\n .split(',')\n .map((name: string): string => name.trim().replace(/^`|`$/g, '').trim())\n .filter((name: string): boolean => name.length > 0);\n}\n\n/** Запускается ли файл сам по себе. Спрашиваем систему, а не разбираем биты режима руками. */\nfunction isExecutable(path: string): boolean {\n try {\n accessSync(path, constants.X_OK);\n\n return true;\n } catch {\n return false;\n }\n}\n\n/** Заголовок нужен человеку, который выбирает: имена законов ему ни о чём не говорят. */\nexport function titleOf(text: string, fallback: string): string {\n const found: RegExpMatchArray | null = text.match(TITLE);\n\n return found ? found[1].trim() : fallback;\n}\n\n/**\n * Файлы рода — вместе с теми, что лежат в подкаталогах.\n *\n * Обход рекурсивный потому, что у законов два слоя: общий закон верен любому приложению этого\n * класса, а закон приложения — только такому, где есть деньги, локали или владеющая сущность.\n * Слой отражён каталогом, и путь внутри рода едет в идентификатор целиком, чтобы разложенное\n * повторяло раскладку пакета.\n */\nfunction filesOf(dir: string, prefix: string = ''): readonly string[] {\n let entries: readonly Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const files: string[] = [];\n for (const entry of [...entries].sort((one: Dirent, two: Dirent): number => one.name.localeCompare(two.name))) {\n if (entry.isFile()) {\n files.push(`${prefix}${entry.name}`);\n } else if (entry.isDirectory()) {\n files.push(...filesOf(join(dir, entry.name), `${prefix}${entry.name}/`));\n }\n }\n\n return files;\n}\n\n/**\n * Всё, что лежит в ресурсах пакета. Список не выписан руками, а читается из каталога:\n * выписанный разошёлся бы с ним на первом же добавленном законе, и заметить это было бы\n * нечем — ресурс просто не приезжал бы к проекту.\n */\nexport function readCatalog(assetsDir: string): readonly IEntryOfCatalog[] {\n const axes: readonly IAxis[] = readAxes(assetsDir);\n const entries: IEntryOfCatalog[] = [];\n\n for (const kind of KINDS) {\n for (const file of filesOf(join(assetsDir, kind))) {\n const path: string = join(assetsDir, kind, file);\n const text: string = readFileSync(path, 'utf8');\n const variant: IVariant | null = variantOf(file, axes);\n const needs: string | null = requirementOf(withoutVariant(file, variant));\n const name: string = withoutRequirement(withoutVariant(file, variant), needs).replace(/\\.[^./]+$/, '');\n const executable: boolean = isExecutable(path);\n entries.push({\n id: `${kind}/${file}`,\n kind,\n name,\n title: titleOf(text, name),\n variant,\n text,\n needs,\n executable,\n requires: requiresOf(text),\n });\n }\n }\n\n return entries;\n}\n\n/** Чем проект ограничил раскладку: выбором, отказом, видами и свойствами, которые он назвал. */\nexport interface ISelection {\n readonly only: readonly string[];\n readonly skip: readonly string[];\n readonly variants: Readonly<Record<string, string>>;\n /**\n * Свойства дерева: что у него есть. Пусто и не названо вовсе — одно и то же: дерево о себе\n * ничего не сказало и помеченного требованием не получает.\n */\n readonly has?: readonly string[];\n}\n\n/**\n * Взят ли ресурс при этом выборе.\n *\n * `only` ограничивает **только те роды, которые сам называет**. Проект, выбравший девять\n * законов из пятнадцати, шаблоны при этом не терял бы: перечисляя законы, он говорит о\n * законах, а не обо всём, что пакет везёт. Обратное правило читалось бы как «выбрал законы —\n * остался без шаблонов», и заметил бы это только тот, кто пошёл за шаблоном.\n *\n * `skip` вычитает из выбранного. Отказ от одного закона не должен требовать переписать\n * список из пятнадцати, а точечное исключение остаётся точечным.\n *\n * Вид отбирается раньше обоих: ресурс чужого хостинга не «не выбран» — его в этом дереве не\n * существует вовсе, и называть его в `skip` проекту незачем.\n *\n * Требование к свойству дерева слабее выбора поимённо: дерево, назвавшее ресурс словом, знает\n * про своё дерево больше, чем пакет выводит по признаку. Молчаливым такое исключение не\n * остаётся — неотвеченное требование называет перечень раскладки.\n */\nexport function isChosen(entry: IEntryOfCatalog, selection: ISelection): boolean {\n if (!matchesVariant(entry.variant, selection.variants)) {\n return false;\n }\n if (selection.skip.includes(entry.id)) {\n return false;\n }\n const restricted: boolean = selection.only.some((id: string): boolean => id.startsWith(`${entry.kind}/`));\n if (restricted) {\n return selection.only.includes(entry.id);\n }\n\n return answersRequirement(entry.needs, selection.has ?? []);\n}\n\n/** Ресурс, у которого виды есть, но ни один не отвечает тому, что выбрало дерево. */\nexport interface IGapOfVariant {\n readonly kind: TKind;\n /** Имя без вида — под ним ресурс лёг бы в дерево. */\n readonly name: string;\n readonly axis: string;\n /** Что выбрало дерево по этой оси. */\n readonly chosen: string;\n /** Виды, которые у ресурса есть на самом деле. */\n readonly available: readonly string[];\n /** Идентификаторы этих видов: ими же пробел и снимается через `skip`. */\n readonly ids: readonly string[];\n}\n\n/**\n * Ресурсы, оставшиеся без вида под выбор дерева.\n *\n * Молчаливый пропуск такого ресурса — худший из исходов. Правило поставки едет тремя видами, а\n * команды заведения задачи и сверки очереди — одним: дерево, выбравшее чужой хостинг, получает\n * правило, которое зовёт три команды, и ни одного скрипта под ними. Отказа при этом не бывает —\n * ресурс просто не приезжает, и узнают об этом, когда команда из правила не находится.\n *\n * Отсюда правило: **ресурс с видами, ни один из которых не совпал, — отказ, а не пропуск.**\n * Снимается он двумя способами, и оба явные: завести недостающий вид в пакете либо назвать\n * существующие в `skip` — дерево тем самым говорит, что обходится без этого ресурса.\n */\nexport function variantGaps(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IGapOfVariant[] {\n const groups: Map<string, IEntryOfCatalog[]> = new Map();\n for (const entry of catalog) {\n if (entry.variant === null) {\n continue;\n }\n const key: string = `${entry.kind}/${entry.name}`;\n groups.set(key, [...(groups.get(key) ?? []), entry]);\n }\n\n const gaps: IGapOfVariant[] = [];\n for (const entries of groups.values()) {\n const axis: string = entries[0].variant?.axis ?? '';\n if (entries.some((entry: IEntryOfCatalog): boolean => matchesVariant(entry.variant, selection.variants))) {\n continue;\n }\n // Отказ от ресурса — законный ответ, и повторять его отказом раскладки незачем: дерево\n // уже сказало, что обходится без него.\n if (entries.every((entry: IEntryOfCatalog): boolean => selection.skip.includes(entry.id))) {\n continue;\n }\n // Род, суженный через `only`, отбирает ресурсы поимённо: не названный в нём ресурс не\n // пропал — его не просили.\n const restricted: boolean = selection.only.some((id: string): boolean => id.startsWith(`${entries[0].kind}/`));\n if (restricted && !entries.some((entry: IEntryOfCatalog): boolean => selection.only.includes(entry.id))) {\n continue;\n }\n gaps.push({\n kind: entries[0].kind,\n name: entries[0].name,\n axis,\n chosen: selection.variants[axis] ?? '',\n available: entries.map((entry: IEntryOfCatalog): string => entry.variant?.value ?? ''),\n ids: entries.map((entry: IEntryOfCatalog): string => entry.id),\n });\n }\n\n return gaps;\n}\n\n/** Ресурс, снятый вслед за отвергнутым родителем. */\nexport interface ICascadeCut {\n readonly id: string;\n /** Ближайший родитель: у правила — его закон, у паттерна — его правило. */\n readonly parent: string;\n /**\n * Отвергнутый корень цепочки — то, что дерево действительно отвергло или не выбрало.\n * Ближайшего родителя мало: паттерн уходит вслед за правилом, которого в отказе нет, и\n * искать его там читатель пойдёт зря.\n */\n readonly root: string;\n}\n\n/** Последнее звено имени: `application/money` → `money`. Им ресурсы и ссылаются друг на друга. */\nconst shortNameOf: (entry: IEntryOfCatalog) => string = (entry: IEntryOfCatalog): string => entry.name.split('/').pop() ?? entry.name;\n\n/**\n * Ресурсы, снятые каскадом: правила при невзятом законе и паттерны при невзятых правилах.\n *\n * Связь читается из вступления самого ресурса — отдельный список при пакете разошёлся бы с\n * ресурсами молча, тем же молчанием, ради которого каскад и заводится. Родитель ищется по\n * последнему звену имени: полного пути в шапке нет, потому что переезд закона между слоями\n * переписывал бы шапки всех правил при нём.\n *\n * Родителя в каталоге нет вовсе — каскад молчит: снимать не по чему, а промах в шапке судит\n * сверка связности пакета, до всякой раскладки в дереве.\n */\nexport function cascadeCuts(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly ICascadeCut[] {\n const takenOf: (kind: TKind) => ReadonlySet<string> = (kind: TKind): ReadonlySet<string> =>\n new Set(catalog.filter((entry: IEntryOfCatalog): boolean => entry.kind === kind && isChosen(entry, selection)).map(shortNameOf));\n const knownOf: (kind: TKind) => ReadonlySet<string> = (kind: TKind): ReadonlySet<string> =>\n new Set(catalog.filter((entry: IEntryOfCatalog): boolean => entry.kind === kind).map(shortNameOf));\n\n const takenLaws: ReadonlySet<string> = takenOf('laws');\n const knownLaws: ReadonlySet<string> = knownOf('laws');\n const takenRules: ReadonlySet<string> = takenOf('rules');\n const knownRules: ReadonlySet<string> = knownOf('rules');\n\n const cuts: ICascadeCut[] = [];\n /** Правило, снятое законом: для паттернов при нём корнем цепочки будет этот закон. */\n const rootByRule: Map<string, string> = new Map<string, string>();\n\n for (const entry of catalog) {\n if (entry.kind !== 'rules' || !isChosen(entry, selection)) {\n continue;\n }\n const law: string = frontMatterOf(entry.text).law;\n if (!law || !knownLaws.has(law) || takenLaws.has(law)) {\n continue;\n }\n cuts.push({ id: entry.id, parent: law, root: law });\n rootByRule.set(shortNameOf(entry), law);\n }\n\n for (const entry of catalog) {\n if (entry.kind !== 'patterns' || !isChosen(entry, selection)) {\n continue;\n }\n const rule: string = frontMatterOf(entry.text).rule;\n if (!rule || !knownRules.has(rule) || (takenRules.has(rule) && !rootByRule.has(rule))) {\n continue;\n }\n cuts.push({ id: entry.id, parent: rule, root: rootByRule.get(rule) ?? rule });\n }\n\n return cuts;\n}\n\n/**\n * Ресурсы, названные выбором поимённо и всё равно снятые каскадом.\n *\n * Дерево, назвавшее правило в выборе при невыбранном законе, не получает его молча — то самое\n * молчание, ради которого каскад и заводится, только с другой стороны: там о снятии не знал тот,\n * кто ничего не просил, здесь — тот, кто попросил прямо.\n */\nexport function namedButCut(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly ICascadeCut[] {\n return cascadeCuts(catalog, selection).filter((one: ICascadeCut): boolean => selection.only.includes(one.id));\n}\n\n/**\n * Что ложится в дерево: прямой отбор за вычетом снятого каскадом.\n *\n * Отдельной функцией, а не внутри `isChosen`, потому что каскад знает весь каталог, а отбор —\n * один ресурс: связь родителя с потомком по одной записи не видна.\n */\nexport function chosenEntries(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IEntryOfCatalog[] {\n const cut: ReadonlySet<string> = new Set(cascadeCuts(catalog, selection).map((one: ICascadeCut): string => one.id));\n\n return catalog.filter((entry: IEntryOfCatalog): boolean => isChosen(entry, selection) && !cut.has(entry.id));\n}\n\n/** Строка отказа, которая ничего не снимает. */\nexport interface IIdleSkip {\n readonly id: string;\n /** Чем названное ею уже снято: имя отвергнутого родителя, либо пусто — такого ресурса нет. */\n readonly by: string;\n}\n\n/**\n * Строки отказа, которые ничего не снимают: названное ими снято каскадом или в каталоге не\n * значится вовсе.\n *\n * Не отказ, а предупреждение: настройка, верная до обновления пакета, становится лишней сама,\n * без единой правки в дереве, и отбивать за это раскладку значило бы наказывать за вчерашнюю\n * правоту.\n *\n * Строка на ресурс чужого вида лишней не считается никогда: ею дерево гасит отказ о ресурсе,\n * у которого нет подходящего вида, — то есть работу она делает.\n */\nexport function idleSkips(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IIdleSkip[] {\n const idle: IIdleSkip[] = [];\n\n for (const id of selection.skip) {\n const entry: IEntryOfCatalog | undefined = catalog.find((one: IEntryOfCatalog): boolean => one.id === id);\n if (!entry) {\n idle.push({ id, by: '' });\n continue;\n }\n if (!matchesVariant(entry.variant, selection.variants)) {\n continue;\n }\n // Каскад считается от отбора без этой строки: иначе снятое ею самой читалось бы как\n // снятое родителем, и лишней оказалась бы любая строка отказа подряд.\n const without: ISelection = { ...selection, skip: selection.skip.filter((one: string): boolean => one !== id) };\n const cut: ICascadeCut | undefined = cascadeCuts(catalog, without).find((one: ICascadeCut): boolean => one.id === id);\n if (cut) {\n idle.push({ id, by: cut.root });\n }\n }\n\n return idle;\n}\n\n/** Выбранный ресурс, чьё требование в дерево не поехало. */\nexport interface IBrokenLink {\n /** Кто требует — идентификатор выбранного ресурса. */\n readonly id: string;\n /** Чего не хватает — идентификатор требования. */\n readonly requires: string;\n /** Требования нет в пакете вовсе: промах в шапке ресурса, а не выбор дерева. */\n readonly unknown: boolean;\n}\n\n/**\n * Связи, разорванные выбором дерева.\n *\n * Не отказ, а предупреждение — и это главное свойство. Дерево вправе закрыть требование своим\n * средством: паттерн исполним и с чужим хуком, если тот делает то же самое. Отказ здесь отбивал\n * бы законную раскладку, а молчание оставляет дерево исправным на вид — сверка зелена на любом\n * подмножестве, сколько бы связок ни было разорвано.\n */\nexport function brokenLinks(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IBrokenLink[] {\n const known: ReadonlySet<string> = new Set(catalog.map((entry: IEntryOfCatalog): string => entry.id));\n const chosen: ReadonlySet<string> = new Set(\n catalog.filter((entry: IEntryOfCatalog): boolean => isChosen(entry, selection)).map((entry: IEntryOfCatalog): string => entry.id)\n );\n\n const broken: IBrokenLink[] = [];\n for (const entry of catalog) {\n if (!chosen.has(entry.id)) {\n continue;\n }\n for (const required of entry.requires) {\n if (chosen.has(required)) {\n continue;\n }\n broken.push({ id: entry.id, requires: required, unknown: !known.has(required) });\n }\n }\n\n return broken;\n}\n\n/**\n * Идентификатор по тому, как ресурс назвали в строке запуска. Принимаются все формы, которыми\n * его называют вслух: `money`, `application/money`, `money.md` и `laws/application/money.md`.\n */\nexport function idOf(spoken: string, kind: TKind, catalog: readonly IEntryOfCatalog[]): string | null {\n const wanted: string = spoken.trim();\n const shortOf: (entry: IEntryOfCatalog) => string = (entry: IEntryOfCatalog): string => entry.name.split('/').pop() ?? entry.name;\n const found: IEntryOfCatalog | undefined = catalog.find(\n (entry: IEntryOfCatalog): boolean =>\n entry.kind === kind &&\n (entry.id === wanted || entry.name === wanted || shortOf(entry) === wanted || entry.id.endsWith(`/${wanted}`))\n );\n\n return found ? found.id : null;\n}\n\nexport interface IResolvedSelection {\n readonly ids: readonly string[];\n /** Имена, которым в каталоге ничего не отвечает: промах в строке запуска, а не выбор. */\n readonly unknown: readonly string[];\n}\n\n/** Разбор `--laws access,delivery`: имя без ответа — промах, и молчать о нём нельзя. */\nexport function resolveSelection(spoken: readonly string[], kind: TKind, catalog: readonly IEntryOfCatalog[]): IResolvedSelection {\n const ids: string[] = [];\n const unknown: string[] = [];\n\n for (const name of spoken) {\n const id: string | null = idOf(name, kind, catalog);\n if (id === null) {\n unknown.push(name.trim());\n } else if (!ids.includes(id)) {\n ids.push(id);\n }\n }\n\n return { ids, unknown };\n}\n"]}
package/lib/commands.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { IStaleBuild } from './freshness.js';
2
- import { TSubmit } from './submit.js';
3
2
  export interface IOutcomeOfCommand {
4
3
  readonly code: number;
5
4
  readonly lines: readonly string[];
@@ -15,11 +14,6 @@ export interface IEnvironment {
15
14
  * тем каталогом ресурсов, который им дали, и о существовании исходников не знают.
16
15
  */
17
16
  readonly stale?: IStaleBuild | null;
18
- /**
19
- * `владелец/репозиторий` пакета — куда уезжают предложения. Читается из его манифеста на
20
- * краю: зашитый в код адрес назвал бы чужое дерево в текстах пакета.
21
- */
22
- readonly repository?: string;
23
17
  }
24
18
  /** Куда уезжает прежнее содержимое файла, отданного пакету. */
25
19
  export declare const KEPT_SUFFIX: string;
@@ -57,26 +51,6 @@ export interface IStatsOptions {
57
51
  * правил так же ценно, как дополнять.
58
52
  */
59
53
  export declare function stats(env: IEnvironment, options: IStatsOptions): IOutcomeOfCommand;
60
- /** Чем отправка живёт: чем заводить запись, куда и чем себя выдаёт это дерево. */
61
- export interface IProposeOptions {
62
- /** Печатать, что уехало бы, и ничего не отправлять. */
63
- readonly dryRun: boolean;
64
- readonly submit: TSubmit;
65
- /** `владелец/репозиторий` пакета — из его манифеста, а не из кода. */
66
- readonly repository: string;
67
- /** Адрес удалённого репозитория этого дерева: он тоже называет дерево. */
68
- readonly remote: string;
69
- /** Сводка наблюдений, которая едет вместе с предложением: без цифр это мнение. */
70
- readonly summary: readonly string[];
71
- }
72
- /**
73
- * Отправка предложений, адресованных пакету.
74
- *
75
- * Наружу уезжает только адрес «пакет»: «компаньон» и «дерево» — про имена и надстройки этого
76
- * дерева, и в репозитории пакета им делать нечего. Перед отправкой текст сверяется на адрес
77
- * дерева: файл уезжает в чужой репозиторий целиком, и проверка здесь дешевле, чем разбор потом.
78
- */
79
- export declare function propose(env: IEnvironment, options: IProposeOptions): IOutcomeOfCommand;
80
54
  export declare function doctor(env: IEnvironment): IOutcomeOfCommand;
81
55
  /**
82
56
  * Что пакет везёт и что из этого взято здесь.
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/commands.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAK7C,OAAO,EAAkB,OAAO,EAAE,MAAM,aAAa,CAAC;AAKtD,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAChC;AAuDD,+DAA+D;AAC/D,eAAO,MAAM,WAAW,EAAE,MAAyB,CAAC;AAoQpD;;;;GAIG;AACH,wBAAgB,IAAI,CAChB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,SAAS,MAAM,EAAO,EAC5B,QAAQ,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC/C,SAAS,GAAE,MAAW,GACvB,iBAAiB,CAgCnB;AAgBD,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,iBAAiB,CA+EzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,CA+CpF;AAQD,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CAC1B;AAeD;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,GAAG,iBAAiB,CA2FlF;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAuBD;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,GAAG,iBAAiB,CAkGtF;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CA+D3D;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAsDzD"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/commands.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAU7C,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CACvC;AAyDD,+DAA+D;AAC/D,eAAO,MAAM,WAAW,EAAE,MAAyB,CAAC;AAuSpD;;;;GAIG;AACH,wBAAgB,IAAI,CAChB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,SAAS,MAAM,EAAO,EAC5B,QAAQ,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,EAC/C,SAAS,GAAE,MAAW,GACvB,iBAAiB,CAgCnB;AAsCD,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAwFzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,CA+CpF;AAQD,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CAC1B;AASD;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,GAAG,iBAAiB,CA2FlF;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAyE3D;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CA2DzD"}
package/lib/commands.js CHANGED
@@ -7,14 +7,14 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from '
7
7
  import { join } from 'node:path';
8
8
  import { collectAssets } from './assets.js';
9
9
  import { cascadeCuts, isChosen, readCatalog } from './catalog.js';
10
- import { isUnfilled } from './companion.js';
11
- import { CONFIG_PATH, DEFAULT_LAYOUT, KINDS, OVERRIDES_DIR, PROFILE_FILE, readConfig, RT_KIT_DIR, SKILL_KINDS, } from './config.js';
10
+ import { debtLine, isUnfilled, pathOf as companionPathOf, unaddressedOf } from './companion.js';
11
+ import { CONFIG_PATH, DEFAULT_LAYOUT, KINDS, OVERRIDES_DIR, PROFILE_FILE, readConfig, RT_KIT_DIR } from './config.js';
12
12
  import { hooksSection, SETTINGS_PATH } from './hooks-map.js';
13
13
  import { DEFAULT_DAYS, KEEP_DAYS, OBSERVATIONS_DIR, readObservations, summarize } from './observations.js';
14
14
  import { isRefusal } from './plan.js';
15
- import { leaksIn, markSent, marksOf, PROPOSALS_DIR, readProposals, TO_PACKAGE } from './proposals.js';
16
- import { FEEDBACK_LABEL } from './submit.js';
15
+ import { laidOutSkills } from './snapshot.js';
17
16
  import { pendingOf, planSync, runSync } from './sync.js';
17
+ import { answersRequirement, readTraits, unknownTraits } from './traits.js';
18
18
  import { placeholdersOf } from './vars.js';
19
19
  import { readAxes, unansweredAxes } from './variants.js';
20
20
  /** Что с файлом сделала раскладка. Прошедшее время здесь правда: `sync` уже записал. */
@@ -43,6 +43,8 @@ const CUT_BY_CASCADE = 'снят каскадом';
43
43
  const SKIPPED = 'пропущен';
44
44
  /** Ресурс чужого вида: в этом дереве его не существует, а не «от него отказались». */
45
45
  const OTHER_VARIANT = 'другой вид';
46
+ /** Ресурс, которому нужно свойство дерева: дерево его не отвергало — свойства у него нет. */
47
+ const NEEDS_TRAIT = 'нужно свойство';
46
48
  const KIND_TITLE = {
47
49
  laws: 'ЗАКОНЫ',
48
50
  rules: 'ПРАВИЛА',
@@ -135,6 +137,17 @@ function profileLines(root, assetsDir, config) {
135
137
  }
136
138
  const holes = (result) => [...result.missing].map(([asset, names]) => ` ${asset}: нет значений для ${names.map((name) => `{{${name}}}`).join(', ')}`);
137
139
  const unfilled = (result) => result.companions.filter(isUnfilled);
140
+ /**
141
+ * Строка про компаньон, которому нечего сказать.
142
+ *
143
+ * У правила, требующего свойства, которого дерево не назвало, это не черновик, а лишний файл:
144
+ * заполнять его нечем — ни одной статье такого правила здесь не отвечает ни файл, ни символ.
145
+ * Такое правило доезжает сюда только выбором поимённо, и верное действие тут обратное
146
+ * заполнению — снять ресурс строкой отказа.
147
+ */
148
+ const companionLine = (entry) => entry.needs === null
149
+ ? ` ${entry.path} — ${COMPANION_WORD[entry.state]}`
150
+ : ` ${entry.path} — правилу нужно свойство «${entry.needs}»: заполнять нечем, снимай ресурс строкой в \`skip\``;
138
151
  /** Ось без ответа: чем её спрашивают и из чего выбирают. */
139
152
  const axisLines = (axes) => axes.flatMap((axis) => [
140
153
  ` ${axis.name} — ${axis.question}`,
@@ -161,6 +174,23 @@ const gapLines = (result) => result.gaps.flatMap((gap) => [
161
174
  ` ${gap.kind}/${gap.name} — есть только под ${gap.axis}: ${gap.available.join(', ')}, а выбран «${gap.chosen}»`,
162
175
  ` либо заведи вид под «${gap.chosen}», либо назови в skip: ${gap.ids.join(', ')}`,
163
176
  ]);
177
+ /**
178
+ * Свойства, названные не по перечню пакета, — и деревом, и ресурсами.
179
+ *
180
+ * Обе стороны собираются одной функцией, потому что промах у них общий: имя свойства написано
181
+ * так, как его никто не объявлял. Разница только в том, где оно написано, и потому в строке
182
+ * отказа стоит место, а не одно имя.
183
+ */
184
+ function strangeTraits(config, assetsDir) {
185
+ const traits = readTraits(assetsDir);
186
+ const lines = unknownTraits(config.has, traits).map((trait) => ` \`${trait}\` — названо деревом в \`has\`, а пакет такого свойства не объявлял`);
187
+ for (const entry of readCatalog(assetsDir)) {
188
+ if (entry.needs !== null && unknownTraits([entry.needs], traits).length) {
189
+ lines.push(` \`${entry.needs}\` — требует ${entry.id}, а пакет такого свойства не объявлял`);
190
+ }
191
+ }
192
+ return lines.length ? [...lines, ` объявленные свойства: ${traits.map((trait) => trait.value).join(', ')}`] : [];
193
+ }
164
194
  /**
165
195
  * Гарды, которых нет в настройке агента, и готовый кусок для неё.
166
196
  *
@@ -265,7 +295,7 @@ const describe = (result) => [
265
295
  ...unboundLines(result),
266
296
  ...warnings(result),
267
297
  ...pendingOf(result).map((entry) => ` ${entry.path} — ${STATE_WORD[entry.outcome]}`),
268
- ...unfilled(result).map((entry) => ` ${entry.path} — ${COMPANION_WORD[entry.state]}`),
298
+ ...unfilled(result).map(companionLine),
269
299
  ];
270
300
  /** Имена дырок во всех ресурсах, которые дерево берёт. Без конфига — ни одной: выбор неизвестен. */
271
301
  function placeholdersIn(config, assetsDir) {
@@ -317,6 +347,26 @@ export function init(root, only = [], variants = {}, assetsDir = '') {
317
347
  ],
318
348
  };
319
349
  }
350
+ /**
351
+ * Долг, добавленный этой раскладкой: статьи разложенных правил, у которых в компаньонах дерева
352
+ * нет адреса.
353
+ *
354
+ * Называется он здесь, а не отдельной сверкой, ровно потому, что через день его уже не отличить
355
+ * от накопленного: сверка видит расхождение, но не знает, чьим обновлением оно приехало.
356
+ */
357
+ function debtLines(config, root, assetsDir) {
358
+ const found = [];
359
+ for (const asset of collectAssets(config, assetsDir)) {
360
+ if (asset.kind !== 'rules') {
361
+ continue;
362
+ }
363
+ const path = join(root, companionPathOf(asset));
364
+ const existing = existsSync(path) ? readFileSync(path, 'utf8') : null;
365
+ found.push(unaddressedOf(asset.name, asset.text, existing));
366
+ }
367
+ const line = debtLine(found);
368
+ return line === null ? [] : [line, 'адрес статье дописывается в компаньоне рядом с правилом'];
369
+ }
320
370
  /**
321
371
  * Отказ на устаревшей сборке. Раскладка из неё положила бы прежнюю редакцию ресурса и назвала
322
372
  * это сделанным: неправда дороже отказа — её замечают, когда правленое правило не действует.
@@ -346,6 +396,13 @@ export function sync(env, check) {
346
396
  if (unanswered.length) {
347
397
  return { code: 1, lines: ['раскладка не начата: не выбран вид', ...axisLines(unanswered)] };
348
398
  }
399
+ // Незнакомое свойство — опечатка, и молчать о ней нельзя ни с одной стороны. У дерева она
400
+ // означает, что помеченного им ресурса оно не получит вовсе; у ресурса — что он не ляжет
401
+ // никуда и никогда, а причину в имени файла не разглядеть.
402
+ const strange = strangeTraits(config, assetsDir);
403
+ if (strange.length) {
404
+ return { code: 1, lines: ['раскладка не начата: свойство дерева не объявлено пакетом', ...strange] };
405
+ }
349
406
  if (check) {
350
407
  const result = planSync(config, root, version, assetsDir);
351
408
  const pending = pendingOf(result);
@@ -399,6 +456,7 @@ export function sync(env, check) {
399
456
  ? [`разложено файлов: ${result.written.length}`, ...result.written.map((path) => ` ${path}`)]
400
457
  : ['всё уже разложено']),
401
458
  ...unboundLines(result),
459
+ ...debtLines(config, root, assetsDir),
402
460
  ...warnings(result),
403
461
  ],
404
462
  };
@@ -466,10 +524,6 @@ const countLines = (counted) => {
466
524
  const width = counted.reduce((found, entry) => Math.max(found, entry.name.length), 0);
467
525
  return counted.map((entry) => ` ${entry.name.padEnd(width)} ${entry.count}`);
468
526
  };
469
- /** Имена скилов, разложенных в это дерево: правила, паттерны и скилы без закона. */
470
- const skillsOf = (config, assetsDir) => collectAssets(config, assetsDir)
471
- .filter((asset) => SKILL_KINDS.includes(asset.kind))
472
- .map((asset) => asset.name);
473
527
  /**
474
528
  * Сводка наблюдений за отрезок дней.
475
529
  *
@@ -496,7 +550,7 @@ export function stats(env, options) {
496
550
  }
497
551
  const days = options.days > 0 ? options.days : DEFAULT_DAYS;
498
552
  const result = readObservations(root, options.today, days);
499
- const summary = summarize(result.observations, skillsOf(config, assetsDir), days);
553
+ const summary = summarize(result.observations, laidOutSkills(config, assetsDir), days);
500
554
  if (options.json) {
501
555
  return { code: 0, lines: [JSON.stringify({ ...summary, swept: result.swept })] };
502
556
  }
@@ -530,7 +584,7 @@ export function stats(env, options) {
530
584
  ...(summary.unused.length
531
585
  ? [
532
586
  '',
533
- `разложено и не загружено ни разу: ${summary.unused.length} из ${skillsOf(config, assetsDir).length}`,
587
+ `разложено и не загружено ни разу: ${summary.unused.length} из ${laidOutSkills(config, assetsDir).length}`,
534
588
  ...summary.unused.slice(0, UNUSED_SHOWN).map((name) => ` ${name}`),
535
589
  // Список говорится не весь, и об этом говорится вслух: молчаливый обрыв
536
590
  // читается как «вот они все», и правило, не попавшее в первую дюжину,
@@ -563,115 +617,6 @@ export function stats(env, options) {
563
617
  ],
564
618
  };
565
619
  }
566
- /** Тело записи: место, повод, готовый текст — и сводка под ними. */
567
- const issueBody = (proposal, summary, version) => [
568
- `Ресурс: \`${proposal.resource}\``,
569
- `Пакет: v${version}`,
570
- '',
571
- proposal.body,
572
- '',
573
- '## Наблюдения дерева, из которого пришло предложение',
574
- '',
575
- '```',
576
- ...summary,
577
- '```',
578
- '',
579
- 'Заведено `agent-kit propose`. Дерево, из которого оно пришло, здесь не называется намеренно.',
580
- ].join('\n');
581
- /**
582
- * Отправка предложений, адресованных пакету.
583
- *
584
- * Наружу уезжает только адрес «пакет»: «компаньон» и «дерево» — про имена и надстройки этого
585
- * дерева, и в репозитории пакета им делать нечего. Перед отправкой текст сверяется на адрес
586
- * дерева: файл уезжает в чужой репозиторий целиком, и проверка здесь дешевле, чем разбор потом.
587
- */
588
- export function propose(env, options) {
589
- const { root, version } = env;
590
- const config = readConfig(root);
591
- if (!config) {
592
- return { code: 1, lines: [NO_CONFIG] };
593
- }
594
- if (!options.repository) {
595
- return { code: 1, lines: ['куда отправлять — неизвестно: в манифесте пакета нет адреса репозитория'] };
596
- }
597
- const all = readProposals(root);
598
- const mine = all.filter((entry) => entry.address === TO_PACKAGE && !entry.sent);
599
- if (!mine.length) {
600
- const others = all.filter((entry) => entry.address !== TO_PACKAGE).length;
601
- const sent = all.filter((entry) => Boolean(entry.sent)).length;
602
- return {
603
- code: 0,
604
- lines: [
605
- 'отправлять нечего: предложений с адресом «пакет» и без пометки об отправке нет',
606
- ...(others ? [`с другими адресами: ${others} — они правятся здесь же, надстройкой и компаньоном`] : []),
607
- ...(sent ? [`уже отправлено: ${sent}`] : []),
608
- ...(all.length ? [] : [`предложения кладутся в ${PROPOSALS_DIR}/ — форма в шаблоне \`proposal.md\``]),
609
- ],
610
- };
611
- }
612
- // Утечка отбивает отправку целиком, а не свой блок: файл разбирает человек, и «уехало два из
613
- // трёх» он прочтёт как «всё в порядке».
614
- const marks = marksOf(root, options.remote);
615
- const leaked = mine.flatMap((proposal) => leaksIn(proposal.body, marks).map((leak) => ({ proposal, leak })));
616
- if (leaked.length) {
617
- return {
618
- code: 1,
619
- lines: [
620
- `отправка не начата: в тексте предложений назван адрес этого дерева — ${leaked.length}`,
621
- ...leaked.map(({ proposal, leak }) => ` ${proposal.file}:${proposal.line + leak.line} — ${leak.why}\n ${leak.text}`),
622
- 'предложение уезжает в чужой репозиторий целиком: правь текст, а не обходи проверку',
623
- ],
624
- };
625
- }
626
- if (options.dryRun) {
627
- return {
628
- code: 0,
629
- lines: [
630
- `уехало бы записей: ${mine.length} → ${options.repository}, метка ${FEEDBACK_LABEL}`,
631
- ...mine.map((entry) => ` ${entry.resource} (${entry.file}:${entry.line})`),
632
- 'сводка наблюдений едет вместе с ними',
633
- ],
634
- };
635
- }
636
- const done = [];
637
- for (const proposal of mine) {
638
- let url = '';
639
- try {
640
- url = options.submit({
641
- repository: options.repository,
642
- title: `предложение: ${proposal.resource}`,
643
- body: issueBody(proposal, options.summary, version),
644
- label: FEEDBACK_LABEL,
645
- });
646
- }
647
- catch (error) {
648
- return {
649
- code: 1,
650
- lines: [
651
- ...(done.length ? [`отправлено до отказа: ${done.length}`, ...done] : []),
652
- `отправка оборвалась на ${proposal.resource}: ${error.message.split('\n')[0]}`,
653
- // Три причины покрывают почти все отказы, и ни одну из них не видно из
654
- // сообщения помощника: он говорит про метку, доступ и сам себя одинаково глухо.
655
- `проверь: помощник хостинга стоит и вошёл в учётную запись; в ${options.repository} заведена метка ${FEEDBACK_LABEL}; у учётной записи есть право заводить записи`,
656
- 'файл предложений остался на месте — отправленное помечено, остальное уедет повторным запуском',
657
- ],
658
- };
659
- }
660
- // Пометка ложится сразу, а не после всех: оборвавшаяся отправка иначе увозит половину
661
- // предложений и не оставляет следа, по которому видно, какую именно.
662
- const path = join(root, proposal.file);
663
- writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, url), 'utf8');
664
- done.push(` ${proposal.resource} → ${url}`);
665
- }
666
- return {
667
- code: 0,
668
- lines: [
669
- `заведено записей: ${done.length} в ${options.repository}`,
670
- ...done,
671
- 'разбирается сведением: `/agent-kit-digest` в репозитории пакета — оно и заводит задачу',
672
- ],
673
- };
674
- }
675
620
  export function doctor(env) {
676
621
  const { root, version, assetsDir } = env;
677
622
  const config = readConfig(root);
@@ -698,15 +643,21 @@ export function doctor(env) {
698
643
  // не отвергало — оно ушло вслед за родителем, и искать его в отказе читатель пойдёт зря.
699
644
  const cuts = cascadeCuts(catalog, config);
700
645
  const cut = new Set(cuts.map((one) => one.id));
646
+ // Ресурс с неотвеченным требованием в «не выбрано» не идёт по той же причине, что и чужой
647
+ // вид: дерево его не отвергало — свойства, без которого он бессмыслен, у него просто нет.
648
+ const needing = catalog.filter((entry) => entry.needs !== null && !answersRequirement(entry.needs, config.has) && !config.skip.includes(entry.id));
649
+ const needsTrait = needing.length;
701
650
  // Невыбранное называется поимённо: число «не выбрано: 73» не отвечает ни на один вопрос,
702
651
  // ради которого его читают, — ни какого ресурса не хватает, ни требуется ли он соседу.
703
652
  const unchosen = catalog.filter((entry) => !isChosen(entry, config) &&
704
653
  !config.skip.includes(entry.id) &&
705
- foreignVariant.every((one) => one.id !== entry.id));
654
+ foreignVariant.every((one) => one.id !== entry.id) &&
655
+ needing.every((one) => one.id !== entry.id));
706
656
  const lines = [
707
657
  `пакет v${version}, везёт ресурсов ${catalog.length}, взято ${taken}`,
708
- `не выбрано: ${catalog.length - taken - skipped - other - cut.size}, пропущено: ${skipped}, другой вид: ${other}, снято каскадом: ${cut.size}`,
658
+ `не выбрано: ${catalog.length - taken - skipped - other - cut.size - needsTrait}, пропущено: ${skipped}, другой вид: ${other}, снято каскадом: ${cut.size}, нужно свойство: ${needsTrait}`,
709
659
  ...unchosen.map((entry) => ` не выбран: ${entry.id}`),
660
+ ...needing.map((entry) => ` нужно свойство «${entry.needs}»: ${entry.id}`),
710
661
  ...cuts.map((one) => ` снят каскадом: ${one.id} — вслед за ${one.parent}${one.parent === one.root ? '' : `, отвергнут ${one.root}`}`),
711
662
  ...profileLines(root, assetsDir, config),
712
663
  `значений в конфиге: ${Object.keys(config.vars).length}`,
@@ -747,6 +698,11 @@ export function list(env) {
747
698
  if (config.skip.includes(entry.id)) {
748
699
  return SKIPPED;
749
700
  }
701
+ // Неотвеченное требование — не отказ дерева: оно этого ресурса не выбирало и не
702
+ // отвергало, а свойства, без которого ресурс бессмыслен, у него просто нет.
703
+ if (entry.needs !== null && !answersRequirement(entry.needs, config.has)) {
704
+ return `${NEEDS_TRAIT}: ${entry.needs}`;
705
+ }
750
706
  if (!isChosen(entry, config)) {
751
707
  return NOT_CHOSEN;
752
708
  }