@rt-tools/agent-kit 0.8.3 → 0.9.1

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 (132) hide show
  1. package/README.md +21 -0
  2. package/assets/checks/board.github.mjs +48 -1
  3. package/assets/checks/check-board.github.mjs +84 -1
  4. package/assets/checks/check-lib-layers.mjs +13 -524
  5. package/assets/checks/check-specs.mjs +11 -782
  6. package/assets/checks/check-styles.mjs +185 -15
  7. package/assets/checks/lib-boundaries.mjs +143 -0
  8. package/assets/checks/lib-common.mjs +149 -0
  9. package/assets/checks/lib-domains.mjs +205 -0
  10. package/assets/checks/lib-manifests.mjs +60 -0
  11. package/assets/checks/lib-reexports.mjs +101 -0
  12. package/assets/checks/rt-kit-checks.config.mjs +17 -0
  13. package/assets/checks/spec-anchors.mjs +297 -0
  14. package/assets/checks/spec-common.mjs +222 -0
  15. package/assets/checks/spec-contract.mjs +152 -0
  16. package/assets/checks/spec-scenarios.mjs +201 -0
  17. package/assets/defaults/project.sh +8 -0
  18. package/assets/hooks/git-guard-push-tests.sh +8 -4
  19. package/assets/hooks/skill-gate.sh +1 -1
  20. package/assets/hooks/sql-guard-parse.sh +187 -0
  21. package/assets/hooks/sql-guard-request.sh +117 -0
  22. package/assets/hooks/sql-guard-target.sh +134 -0
  23. package/assets/hooks/sql-guard-write.sh +212 -0
  24. package/assets/hooks/sql-guard.sh +26 -596
  25. package/assets/hooks/waiting-turn-guard.sh +42 -13
  26. package/assets/laws/delivery.md +7 -0
  27. package/assets/laws/work-conduct.md +9 -0
  28. package/assets/patterns/admin-lists-screen.md +25 -14
  29. package/assets/patterns/admin-nav-item.md +1 -1
  30. package/assets/patterns/component-structure-new.md +1 -1
  31. package/assets/patterns/entity-aside.md +4 -2
  32. package/assets/patterns/observability-record.md +9 -0
  33. package/assets/patterns/shared-code-new.md +2 -2
  34. package/assets/patterns/task-flow-close.md +7 -1
  35. package/assets/rules/git-workflow.azure.md +7 -0
  36. package/assets/rules/git-workflow.github.md +41 -0
  37. package/assets/rules/git-workflow.gitlab.md +7 -0
  38. package/assets/rules/lib-layers.md +4 -0
  39. package/assets/rules/lists.md +10 -10
  40. package/assets/rules/shared-code.md +1 -1
  41. package/assets/rules/task-flow.md +47 -7
  42. package/assets/rules/testing.md +31 -0
  43. package/assets/rules/typescript-conventions.md +7 -0
  44. package/assets/skills/agent-kit.md +36 -0
  45. package/assets/templates/proposal.md +21 -0
  46. package/bin/agent-kit.d.ts.map +1 -1
  47. package/bin/agent-kit.js +117 -87
  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.map +1 -1
  54. package/lib/argv.js +6 -4
  55. package/lib/argv.js.map +1 -1
  56. package/lib/assets.d.ts.map +1 -1
  57. package/lib/assets.js +2 -1
  58. package/lib/assets.js.map +1 -1
  59. package/lib/cargo.d.ts +20 -0
  60. package/lib/cargo.d.ts.map +1 -1
  61. package/lib/cargo.js.map +1 -1
  62. package/lib/cascade.d.ts +55 -0
  63. package/lib/cascade.d.ts.map +1 -0
  64. package/lib/cascade.js +131 -0
  65. package/lib/cascade.js.map +1 -0
  66. package/lib/catalog.d.ts +0 -75
  67. package/lib/catalog.d.ts.map +1 -1
  68. package/lib/catalog.js +44 -127
  69. package/lib/catalog.js.map +1 -1
  70. package/lib/commands.d.ts.map +1 -1
  71. package/lib/commands.js +152 -85
  72. package/lib/commands.js.map +1 -1
  73. package/lib/companion.d.ts.map +1 -1
  74. package/lib/companion.js +5 -5
  75. package/lib/companion.js.map +1 -1
  76. package/lib/config.d.ts +2 -0
  77. package/lib/config.d.ts.map +1 -1
  78. package/lib/config.js +7 -5
  79. package/lib/config.js.map +1 -1
  80. package/lib/enroll.d.ts +64 -0
  81. package/lib/enroll.d.ts.map +1 -0
  82. package/lib/enroll.js +170 -0
  83. package/lib/enroll.js.map +1 -0
  84. package/lib/freshness.d.ts.map +1 -1
  85. package/lib/freshness.js +31 -17
  86. package/lib/freshness.js.map +1 -1
  87. package/lib/hooks-map.d.ts +30 -0
  88. package/lib/hooks-map.d.ts.map +1 -1
  89. package/lib/hooks-map.js +80 -18
  90. package/lib/hooks-map.js.map +1 -1
  91. package/lib/integrity.d.ts +1 -2
  92. package/lib/integrity.d.ts.map +1 -1
  93. package/lib/integrity.js +0 -1
  94. package/lib/integrity.js.map +1 -1
  95. package/lib/observations.d.ts.map +1 -1
  96. package/lib/observations.js +25 -12
  97. package/lib/observations.js.map +1 -1
  98. package/lib/order.d.ts +10 -0
  99. package/lib/order.d.ts.map +1 -0
  100. package/lib/order.js +14 -0
  101. package/lib/order.js.map +1 -0
  102. package/lib/picker.d.ts.map +1 -1
  103. package/lib/picker.js +8 -2
  104. package/lib/picker.js.map +1 -1
  105. package/lib/plan.js +1 -1
  106. package/lib/plan.js.map +1 -1
  107. package/lib/proposals.d.ts.map +1 -1
  108. package/lib/proposals.js +25 -8
  109. package/lib/proposals.js.map +1 -1
  110. package/lib/sections.js +1 -1
  111. package/lib/sections.js.map +1 -1
  112. package/lib/ship.d.ts.map +1 -1
  113. package/lib/ship.js +9 -1
  114. package/lib/ship.js.map +1 -1
  115. package/lib/shipment.d.ts.map +1 -1
  116. package/lib/shipment.js +14 -10
  117. package/lib/shipment.js.map +1 -1
  118. package/lib/snapshot.d.ts.map +1 -1
  119. package/lib/snapshot.js +2 -1
  120. package/lib/snapshot.js.map +1 -1
  121. package/lib/stamp.js +1 -1
  122. package/lib/stamp.js.map +1 -1
  123. package/lib/sync.d.ts +12 -2
  124. package/lib/sync.d.ts.map +1 -1
  125. package/lib/sync.js +11 -10
  126. package/lib/sync.js.map +1 -1
  127. package/lib/vars.d.ts.map +1 -1
  128. package/lib/vars.js +2 -3
  129. package/lib/vars.js.map +1 -1
  130. package/package.json +1 -1
  131. package/rt-tools-agent-kit-0.9.1.tgz +0 -0
  132. package/rt-tools-agent-kit-0.8.3.tgz +0 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Доменная сетка: какие каталоги считаются доменами, из каких слоёв состоит каждая их форма и
3
+ * какие либы в них лежат. Отсюда же выходят либы, оказавшиеся вне сетки, и плоские либы
4
+ * бэкенда — обе группы дальше проверяются наравне с доменными.
5
+ *
6
+ * Имя не начинается с `check-`: перебором таких имён умолчание пакета собирает набор гейта
7
+ * пуша, и помощник с ним гейт стал бы гонять как отдельную проверку.
8
+ */
9
+ import {
10
+ API_DOMAIN_LAYERS,
11
+ API_FAMILY,
12
+ COMMON_DOMAIN_LAYERS,
13
+ FAMILIES,
14
+ FEATURE_DOMAIN_LAYERS,
15
+ FLAT_LAYERS,
16
+ LIBS_ROOT,
17
+ LIB_ROOTS,
18
+ allowlist,
19
+ dirsIn,
20
+ isIgnoredLib,
21
+ isLegacyDomain,
22
+ isLegacyLib,
23
+ isLib,
24
+ isNotDomain,
25
+ isSingleLayerDomain,
26
+ report,
27
+ sourceCount,
28
+ } from './lib-common.mjs';
29
+
30
+ /**
31
+ * Домен ли это. Домен заводится под предмет, и предмет виден по тому, что заполнен у
32
+ * домена не один слой: экраны и состояние, запросы и механика, выход к чужому и то,
33
+ * что мы отдаём. Заполнен ровно один — это слот, который заполнит следующая задача, и
34
+ * от домена он не отличим ничем, кроме намерения.
35
+ *
36
+ * Домен вносится строкой в список исключений вместе с причиной, и причина бывает
37
+ * настоящей: сборка PDF со своими шрифтами не едет ни в чей чужой граф зависимостей.
38
+ */
39
+ function checkDomainIsFilled(domainPath, libs) {
40
+ if (isSingleLayerDomain(domainPath)) {
41
+ return;
42
+ }
43
+ const filled = libs.filter((libPath) => sourceCount(libPath) > 0);
44
+ if (libs.length > 1 && filled.length === 1) {
45
+ report(
46
+ domainPath,
47
+ `заполнен один слой — \`${filled[0].slice(domainPath.length + 1)}\`: домен от слота этим не отличить. ` +
48
+ 'Либо содержимое переезжает в существующую либу, либо домен вносится строкой с причиной в `singleLayerDomains`'
49
+ );
50
+ }
51
+ }
52
+
53
+ /** Собирает пути всех либ домена и попутно проверяет состав его слоёв */
54
+ function collectDomainLibs(domainPath, isCommon) {
55
+ const expected = isCommon ? COMMON_DOMAIN_LAYERS : FEATURE_DOMAIN_LAYERS;
56
+ const actual = dirsIn(domainPath);
57
+
58
+ const missing = expected.filter((layer) => !actual.includes(layer));
59
+ const extra = actual.filter((layer) => !expected.includes(layer));
60
+ if (missing.length > 0) {
61
+ report(domainPath, `нет слоёв: ${missing.join(', ')}`);
62
+ }
63
+ if (extra.length > 0) {
64
+ report(
65
+ domainPath,
66
+ `лишние каталоги: ${extra.join(', ')}${extra.includes('shell') && isCommon ? ' (у общего домена shell не бывает: он не роутится)' : ''}`
67
+ );
68
+ }
69
+
70
+ const libs = actual.filter((layer) => FLAT_LAYERS.includes(layer) || layer === 'shell').map((layer) => `${domainPath}/${layer}`);
71
+
72
+ if (actual.includes('feature')) {
73
+ const featurePath = `${domainPath}/feature`;
74
+ if (isCommon) {
75
+ libs.push(featurePath);
76
+ } else {
77
+ const screens = dirsIn(featurePath).filter((screen) => isLib(`${featurePath}/${screen}`));
78
+ if (screens.length === 0) {
79
+ report(featurePath, 'у фичевого домена `feature` — каталог с либой на экран, здесь ни одной');
80
+ }
81
+ libs.push(...screens.map((screen) => `${featurePath}/${screen}`));
82
+ }
83
+ }
84
+
85
+ checkDomainIsFilled(domainPath, libs);
86
+
87
+ return libs;
88
+ }
89
+
90
+ /**
91
+ * Домены бэкенда: `<корень либ>/<семья бэкенда>/<домен>` с четырьмя слоями. `feature` бывает и либой,
92
+ * и каталогом с либой на proto-сервис — по тому же правилу, по которому у
93
+ * фичевого домена фронта `feature/<экран>`.
94
+ *
95
+ * Пустой слой — не дефект: `api` пуст у домена, который ни с кем чужим не
96
+ * говорит, `data-access` — у того, что не касается базы. Слой всё равно заводится
97
+ * либой, иначе граница домена объявлена не полностью.
98
+ */
99
+ function collectApiDomainLibs() {
100
+ const libs = [];
101
+
102
+ for (const entry of dirsIn(`${LIBS_ROOT}/${API_FAMILY}`)) {
103
+ const domainPath = `${LIBS_ROOT}/${API_FAMILY}/${entry}`;
104
+ if (isNotDomain(domainPath) || isLegacyDomain(domainPath)) {
105
+ continue;
106
+ }
107
+
108
+ const actual = dirsIn(domainPath);
109
+ const missing = API_DOMAIN_LAYERS.filter((layer) => !actual.includes(layer));
110
+ const extra = actual.filter((layer) => !API_DOMAIN_LAYERS.includes(layer));
111
+ if (missing.length > 0) {
112
+ report(domainPath, `нет слоёв: ${missing.join(', ')}`);
113
+ }
114
+ if (extra.length > 0) {
115
+ report(
116
+ domainPath,
117
+ `лишние каталоги: ${extra.join(', ')}${extra.includes('shell') || extra.includes('ui') ? ' (у бэкенда ui и shell не бывает)' : ''}`
118
+ );
119
+ }
120
+
121
+ const domainLibs = actual
122
+ .filter((layer) => layer !== 'feature' && API_DOMAIN_LAYERS.includes(layer))
123
+ .map((layer) => `${domainPath}/${layer}`);
124
+
125
+ if (actual.includes('feature')) {
126
+ const featurePath = `${domainPath}/feature`;
127
+ if (isLib(featurePath)) {
128
+ domainLibs.push(featurePath);
129
+ } else {
130
+ const services = dirsIn(featurePath).filter((service) => isLib(`${featurePath}/${service}`));
131
+ if (services.length === 0) {
132
+ report(featurePath, '`feature` — либа или каталог с либой на proto-сервис, здесь ни того ни другого');
133
+ }
134
+ domainLibs.push(...services.map((service) => `${featurePath}/${service}`));
135
+ }
136
+ }
137
+
138
+ checkDomainIsFilled(domainPath, domainLibs);
139
+ libs.push(...domainLibs);
140
+ }
141
+
142
+ return libs.sort();
143
+ }
144
+
145
+ /** Все либы доменной сетки обоих семейств */
146
+ function collectAllDomainLibs() {
147
+ const libs = [];
148
+
149
+ for (const family of FAMILIES) {
150
+ for (const entry of dirsIn(`${LIBS_ROOT}/${family}`)) {
151
+ if (entry === 'common') {
152
+ for (const commonDomain of dirsIn(`${LIBS_ROOT}/${family}/common`)) {
153
+ libs.push(...collectDomainLibs(`${LIBS_ROOT}/${family}/common/${commonDomain}`, true));
154
+ }
155
+ continue;
156
+ }
157
+ const domainPath = `${LIBS_ROOT}/${family}/${entry}`;
158
+ if (isNotDomain(domainPath) || isLegacyDomain(domainPath)) {
159
+ continue;
160
+ }
161
+ libs.push(...collectDomainLibs(domainPath, false));
162
+ }
163
+ }
164
+
165
+ return libs.sort();
166
+ }
167
+
168
+ /** Каждая либа на диске должна быть либой доменной сетки или значиться в allowlist */
169
+ function collectStrayLibs(knownLibs) {
170
+ const strays = [];
171
+ const walk = (path) => {
172
+ if (isLegacyDomain(path) || isNotDomain(path)) {
173
+ return;
174
+ }
175
+ if (isLib(path)) {
176
+ if (!knownLibs.includes(path) && !isIgnoredLib(path)) {
177
+ strays.push(path);
178
+ }
179
+ // либа старой раскладки может держать внутри себя новые слои — обход продолжается
180
+ if (!isLegacyLib(path)) {
181
+ return;
182
+ }
183
+ }
184
+ dirsIn(path).forEach((entry) => walk(`${path}/${entry}`));
185
+ };
186
+ LIB_ROOTS.forEach((root) => walk(root));
187
+
188
+ return strays;
189
+ }
190
+
191
+ /**
192
+ * Плоские либы бэкенда: `<корень либ>/<семья бэкенда>/<домен>`. Лесенки слоёв у них нет по
193
+ * устройству — домен это одна либа, — но всё остальное проверяется наравне с
194
+ * доменной сеткой. До сужения исключение по этому корню снимало с них и состав
195
+ * файлов, и тег, и алиас: новая либа заводилась без `vitest.config.mts`, и
196
+ * `nx test` по ней молча не гонял ни одной спеки.
197
+ */
198
+ function collectFlatLibs() {
199
+ return (allowlist.flatLibRoots ?? [])
200
+ .flatMap((root) => dirsIn(root).map((entry) => `${root}/${entry}`))
201
+ .filter((path) => isLib(path))
202
+ .sort();
203
+ }
204
+
205
+ export { collectAllDomainLibs, collectApiDomainLibs, collectFlatLibs, collectStrayLibs };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Сама либа: обязательные файлы, имя проекта, приставка селекторов, тег и алиас импорта —
3
+ * всё, чем `project.json` и `tsconfig.base.json` обязаны совпадать с путём либы на диске.
4
+ *
5
+ * Имя не начинается с `check-`: перебором таких имён умолчание пакета собирает набор гейта
6
+ * пуша, и помощник с ним гейт стал бы гонять как отдельную проверку.
7
+ */
8
+ import { existsSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+
11
+ import { LIB_PREFIX, REQUIRED_FILES, importAlias, projectName, projectTag, readJson, report } from './lib-common.mjs';
12
+ import { ROOT } from './rt-kit-checks.config.mjs';
13
+
14
+ function checkLib(libPath, { requirePrefix = true } = {}) {
15
+ for (const file of REQUIRED_FILES) {
16
+ if (!existsSync(join(ROOT, libPath, file))) {
17
+ report(libPath, `нет файла ${file}`);
18
+ }
19
+ }
20
+
21
+ if (!existsSync(join(ROOT, libPath, 'project.json'))) {
22
+ return;
23
+ }
24
+
25
+ const project = readJson(`${libPath}/project.json`);
26
+ if (project.name !== projectName(libPath)) {
27
+ report(libPath, `имя проекта «${project.name}» не совпадает с путём, ожидается «${projectName(libPath)}»`);
28
+ }
29
+ if (project.sourceRoot !== `${libPath}/src`) {
30
+ report(libPath, `sourceRoot «${project.sourceRoot}» не совпадает с путём`);
31
+ }
32
+ // Приставка — про селекторы компонентов, а у бэкенда компонентов нет. Само слово принадлежит
33
+ // дереву: у каждого оно своё, и зашитое здесь краснело бы на всех либах первого же дерева,
34
+ // назвавшего свои селекторы иначе. Дерево, не назвавшее приставки, этой проверки не получает.
35
+ if (requirePrefix && LIB_PREFIX && project.prefix !== LIB_PREFIX) {
36
+ report(libPath, `prefix «${project.prefix}» вместо обязательного «${LIB_PREFIX}»`);
37
+ }
38
+
39
+ const tags = project.tags ?? [];
40
+ if (tags.length !== 1) {
41
+ report(libPath, `тегов ${tags.length}, а должен быть ровно один: ${projectTag(libPath)}`);
42
+ } else if (tags[0] !== projectTag(libPath)) {
43
+ report(libPath, `тег «${tags[0]}» не совпадает с путём, ожидается «${projectTag(libPath)}»`);
44
+ }
45
+ }
46
+
47
+ function checkAliases(libs) {
48
+ const paths = readJson('tsconfig.base.json').compilerOptions.paths;
49
+ for (const libPath of libs) {
50
+ const alias = importAlias(libPath);
51
+ const target = `./${libPath}/src/index.ts`;
52
+ if (!paths[alias]) {
53
+ report(libPath, `в tsconfig.base.json нет алиаса ${alias}`);
54
+ } else if (paths[alias][0] !== target) {
55
+ report(libPath, `алиас ${alias} указывает на ${paths[alias][0]}, а не на ${target}`);
56
+ }
57
+ }
58
+ }
59
+
60
+ export { checkAliases, checkLib };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Барели и реэкспорты: что либа отдаёт наружу и чем это отличается от собственного объявления.
3
+ *
4
+ * Имя не начинается с `check-`: перебором таких имён умолчание пакета собирает набор гейта
5
+ * пуша, и помощник с ним гейт стал бы гонять как отдельную проверку.
6
+ */
7
+ import { readFileSync, readdirSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+
10
+ import { BARREL_FILES, IGNORED_DIRS, isDir, report } from './lib-common.mjs';
11
+ import { CONFIG, ROOT } from './rt-kit-checks.config.mjs';
12
+
13
+ /** Барель собирает наружу собственные файлы либы, и только они в нём законны */
14
+ const isBarrel = (path) => BARREL_FILES.some((name) => path.endsWith(`/${name}`));
15
+
16
+ /** Файл той же либы адресуется относительным путём, чужая либа — алиасом */
17
+ const isOwnFile = (module) => module.startsWith('.');
18
+
19
+ /** `export { X } from '…'` и `import { X } …` вместе с `export { X };` */
20
+ const REEXPORT_FROM = /^export\s+(?:type\s+)?\{([^}]*)\}\s*from\s*'([^']+)';/gm;
21
+ const BARE_EXPORT = /^export\s+(?:type\s+)?\{([^}]*)\};/gm;
22
+ const IMPORTED_NAMES = /^import\s+(?:type\s+)?\{([^}]*)\}\s*from\s*'([^']+)';/gm;
23
+
24
+ const namesIn = (list) =>
25
+ list
26
+ .split(',')
27
+ .map(
28
+ (part) =>
29
+ part
30
+ .trim()
31
+ .replace(/^type\s+/, '')
32
+ .split(/\s+as\s+/)[0]
33
+ )
34
+ .filter(Boolean);
35
+
36
+ /** Файлы либ, кроме спек: реэкспорт в спеке бессмыслен, а обходить их дешевле */
37
+ function collectSourceFiles() {
38
+ const files = [];
39
+ const walk = (dir) => {
40
+ for (const entry of readdirSync(join(ROOT, dir), { withFileTypes: true })) {
41
+ if (IGNORED_DIRS.includes(entry.name) && entry.name !== 'src') {
42
+ continue;
43
+ }
44
+ const path = `${dir}/${entry.name}`;
45
+ if (entry.isDirectory()) {
46
+ walk(path);
47
+ } else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.spec.ts')) {
48
+ files.push(path);
49
+ }
50
+ }
51
+ };
52
+
53
+ CONFIG.sourceRoots.filter(isDir).forEach(walk);
54
+
55
+ return files;
56
+ }
57
+
58
+ /**
59
+ * Символ, объявленный в другой либе, наружу не проходит.
60
+ *
61
+ * Реэкспорт заводится тогда, когда потребителю не хватает прав на либу-источник,
62
+ * и протаскивает её через соседа мимо границы тегов: по графу зависимостей
63
+ * выходит, что слой её не видит. Ни линт, ни сборка реэкспорт от собственного
64
+ * объявления не отличают.
65
+ *
66
+ * В бареле проверяются только чужие либы: собственные файлы он и собирает.
67
+ * Пока барель пропускался целиком, чужой символ проходил через него молча —
68
+ * так `common/util` отдавала работу с датами всему бэкенду через слой `util`
69
+ * домена Postgres.
70
+ */
71
+ function checkReexports() {
72
+ for (const file of collectSourceFiles()) {
73
+ if (file.includes('/gen/') || file.includes('/generated/')) {
74
+ continue;
75
+ }
76
+
77
+ const ownFilesAllowed = isBarrel(file);
78
+ const text = readFileSync(join(ROOT, file), 'utf8');
79
+ for (const [, list, module] of text.matchAll(REEXPORT_FROM)) {
80
+ if (ownFilesAllowed && isOwnFile(module)) {
81
+ continue;
82
+ }
83
+ report(file, `реэкспорт из \`${module}\`: ${namesIn(list).join(', ')} — потребитель импортирует их у источника`);
84
+ }
85
+
86
+ const imported = new Map();
87
+ for (const [, list, module] of text.matchAll(IMPORTED_NAMES)) {
88
+ namesIn(list).forEach((name) => imported.set(name, module));
89
+ }
90
+ for (const [, list] of text.matchAll(BARE_EXPORT)) {
91
+ for (const name of namesIn(list).filter((name) => imported.has(name))) {
92
+ if (ownFilesAllowed && isOwnFile(imported.get(name))) {
93
+ continue;
94
+ }
95
+ report(file, `реэкспорт \`${name}\` из \`${imported.get(name)}\` — потребитель импортирует его у источника`);
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ export { checkReexports };
@@ -95,6 +95,23 @@ const DEFAULTS = {
95
95
  apiFamily: 'api',
96
96
  /** Область алиасов импорта: `@область/<семья>/<домен>/<слой>` в `tsconfig.base.json`. */
97
97
  importScope: '@app',
98
+ /**
99
+ * Приставка селекторов, обязательная фронтовым либам. Слово принадлежит дереву: названное
100
+ * умолчанием, оно краснело бы на каждой либе первого же дерева, у которого приставка своя.
101
+ * Пусто — приставка не судится вовсе.
102
+ */
103
+ libPrefix: '',
104
+ /**
105
+ * Теги либ, чей список зависимостей обязан оставаться пустым: их барель уезжает в бандл
106
+ * бэкенда, а сборка идёт без tree-shaking. Имена тегов — слова дерева, поэтому умолчание
107
+ * пусто: названное здесь требовало бы описания границ под либу, которой в дереве нет.
108
+ */
109
+ noDependencyTags: [],
110
+ /**
111
+ * Как в этом дереве зовётся барель. Собственный файл в нём законен, вне его — реэкспорт.
112
+ * Дерево, которое публикует пакеты, называет здесь и их публичный вход.
113
+ */
114
+ barrelFiles: ['index.ts'],
98
115
  /**
99
116
  * Признаки боевого хранилища: порт, адрес, имя домена. Проверки туда не ходят ни читать,
100
117
  * ни писать — схема на проде меняется выкаткой. Пусто — признаков нет, и адрес боевым