@rt-tools/agent-kit 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/assets/agents/rules-reviewer.md +83 -0
- package/assets/checks/board.github.mjs +48 -1
- package/assets/checks/check-board.github.mjs +84 -1
- package/assets/checks/check-dupes.mjs +66 -6
- package/assets/checks/check-lib-layers.mjs +13 -524
- package/assets/checks/check-specs.mjs +61 -747
- package/assets/checks/check-styles.mjs +185 -15
- package/assets/checks/lib-boundaries.mjs +143 -0
- package/assets/checks/lib-common.mjs +149 -0
- package/assets/checks/lib-domains.mjs +205 -0
- package/assets/checks/lib-manifests.mjs +60 -0
- package/assets/checks/lib-reexports.mjs +101 -0
- package/assets/checks/rt-kit-checks.config.mjs +26 -0
- package/assets/checks/spec-anchors.mjs +297 -0
- package/assets/checks/spec-common.mjs +222 -0
- package/assets/checks/spec-contract.mjs +152 -0
- package/assets/checks/spec-scenarios.mjs +201 -0
- package/assets/commands/feedback.md +95 -0
- package/assets/commands/rules-review.md +98 -0
- package/assets/commands/skill-curator.md +39 -22
- package/assets/defaults/project.sh +8 -0
- package/assets/docs/GLOSSARY.md +21 -20
- package/assets/hooks/git-guard-push-tests.sh +8 -4
- package/assets/hooks/reuse-first-guard.sh +16 -2
- package/assets/hooks/skill-gate.sh +1 -1
- package/assets/hooks/sql-guard-parse.sh +187 -0
- package/assets/hooks/sql-guard-request.sh +117 -0
- package/assets/hooks/sql-guard-target.sh +134 -0
- package/assets/hooks/sql-guard-write.sh +212 -0
- package/assets/hooks/sql-guard.sh +26 -596
- package/assets/hooks/task-flow-guard.sh +16 -2
- package/assets/hooks/waiting-turn-guard.sh +116 -0
- package/assets/laws/delivery.md +35 -0
- package/assets/laws/project-documentation.md +18 -0
- package/assets/laws/work-conduct.md +25 -0
- package/assets/patterns/admin-lists-screen.md +25 -14
- package/assets/patterns/admin-nav-item.md +1 -1
- package/assets/patterns/component-structure-new.md +1 -1
- package/assets/patterns/entity-aside.md +4 -2
- package/assets/patterns/git-workflow-commit.azure.md +74 -2
- package/assets/patterns/git-workflow-commit.github.md +75 -2
- package/assets/patterns/git-workflow-commit.gitlab.md +75 -4
- package/assets/patterns/git-workflow-docker.md +30 -0
- package/assets/patterns/observability-record.md +9 -0
- package/assets/patterns/shared-code-new.md +2 -2
- package/assets/patterns/task-flow-close.md +160 -47
- package/assets/patterns/task-flow-handoff.md +1 -1
- package/assets/patterns/task-flow-resume.md +3 -3
- package/assets/patterns/task-flow-start.md +32 -5
- package/assets/rules/angular-patterns.md +22 -0
- package/assets/rules/api-layer.md +25 -0
- package/assets/rules/browser-verification.md +32 -0
- package/assets/rules/component-structure.md +21 -0
- package/assets/rules/dependencies.md +22 -0
- package/assets/rules/doc-style.md +24 -0
- package/assets/rules/entity-conventions.needs-admin.md +21 -0
- package/assets/rules/entity-models.md +21 -0
- package/assets/rules/git-workflow.azure.md +57 -1
- package/assets/rules/git-workflow.github.md +89 -2
- package/assets/rules/git-workflow.gitlab.md +56 -1
- package/assets/rules/lib-layers.md +29 -0
- package/assets/rules/lists.md +37 -10
- package/assets/rules/navigation.md +21 -0
- package/assets/rules/observability.needs-app.md +23 -0
- package/assets/rules/permissions.md +23 -0
- package/assets/rules/platform-access.md +21 -0
- package/assets/rules/reuse-first.md +20 -0
- package/assets/rules/seo.md +19 -0
- package/assets/rules/shared-code.md +20 -1
- package/assets/rules/spec-driven.md +32 -0
- package/assets/rules/styling-bem.md +19 -0
- package/assets/rules/task-flow.md +148 -18
- package/assets/rules/testing.md +62 -0
- package/assets/rules/translations.md +21 -0
- package/assets/rules/typescript-conventions.md +28 -0
- package/assets/samples/specs/_template/spec.md +83 -0
- package/assets/samples/tasks/_template/grill.md +28 -0
- package/assets/samples/tasks/_template/plan.md +39 -0
- package/assets/samples/tasks/_template/progress.md +23 -0
- package/assets/skills/agent-kit.md +52 -2
- package/assets/templates/proposal.md +21 -0
- package/assets/templates/rule.md +31 -2
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +115 -87
- package/bin/agent-kit.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/argv.d.ts.map +1 -1
- package/lib/argv.js +6 -4
- package/lib/argv.js.map +1 -1
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +2 -1
- package/lib/assets.js.map +1 -1
- package/lib/cargo.d.ts +20 -0
- package/lib/cargo.d.ts.map +1 -1
- package/lib/cargo.js.map +1 -1
- package/lib/cascade.d.ts +55 -0
- package/lib/cascade.d.ts.map +1 -0
- package/lib/cascade.js +131 -0
- package/lib/cascade.js.map +1 -0
- package/lib/catalog.d.ts +0 -75
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +44 -127
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +153 -85
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts.map +1 -1
- package/lib/companion.js +5 -5
- package/lib/companion.js.map +1 -1
- package/lib/config.d.ts +12 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +11 -5
- package/lib/config.js.map +1 -1
- package/lib/enroll.d.ts +56 -0
- package/lib/enroll.d.ts.map +1 -0
- package/lib/enroll.js +123 -0
- package/lib/enroll.js.map +1 -0
- package/lib/freshness.d.ts.map +1 -1
- package/lib/freshness.js +31 -17
- package/lib/freshness.js.map +1 -1
- package/lib/hooks-map.d.ts +30 -0
- package/lib/hooks-map.d.ts.map +1 -1
- package/lib/hooks-map.js +80 -18
- package/lib/hooks-map.js.map +1 -1
- package/lib/integrity.d.ts +1 -2
- package/lib/integrity.d.ts.map +1 -1
- package/lib/integrity.js +0 -1
- package/lib/integrity.js.map +1 -1
- package/lib/observations.d.ts.map +1 -1
- package/lib/observations.js +25 -12
- package/lib/observations.js.map +1 -1
- package/lib/order.d.ts +10 -0
- package/lib/order.d.ts.map +1 -0
- package/lib/order.js +14 -0
- package/lib/order.js.map +1 -0
- package/lib/picker.d.ts.map +1 -1
- package/lib/picker.js +8 -2
- package/lib/picker.js.map +1 -1
- package/lib/plan.js +1 -1
- package/lib/plan.js.map +1 -1
- package/lib/proposals.d.ts.map +1 -1
- package/lib/proposals.js +25 -8
- package/lib/proposals.js.map +1 -1
- package/lib/sections.js +1 -1
- package/lib/sections.js.map +1 -1
- package/lib/ship.d.ts.map +1 -1
- package/lib/ship.js +9 -1
- package/lib/ship.js.map +1 -1
- package/lib/shipment.d.ts.map +1 -1
- package/lib/shipment.js +14 -10
- package/lib/shipment.js.map +1 -1
- package/lib/snapshot.d.ts.map +1 -1
- package/lib/snapshot.js +2 -1
- package/lib/snapshot.js.map +1 -1
- package/lib/stamp.js +1 -1
- package/lib/stamp.js.map +1 -1
- package/lib/sync.d.ts +12 -2
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +11 -10
- package/lib/sync.js.map +1 -1
- package/lib/vars.d.ts.map +1 -1
- package/lib/vars.js +2 -3
- package/lib/vars.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.9.0.tgz +0 -0
- package/rt-tools-agent-kit-0.8.2.tgz +0 -0
package/lib/commands.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable max-lines -- файл делится задачей RT-849 */
|
|
1
2
|
/**
|
|
2
3
|
* Команды и их вывод. Печать отделена от работы: команда возвращает строки и код возврата,
|
|
3
4
|
* а `process.exit` зовёт только точка входа — иначе ни одну из них нельзя было бы проверить
|
|
@@ -6,7 +7,8 @@
|
|
|
6
7
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
7
8
|
import { join } from 'node:path';
|
|
8
9
|
import { collectAssets } from './assets.js';
|
|
9
|
-
import { cascadeCuts
|
|
10
|
+
import { cascadeCuts } from './cascade.js';
|
|
11
|
+
import { isChosen, readCatalog } from './catalog.js';
|
|
10
12
|
import { debtLine, isUnfilled, pathOf as companionPathOf, unaddressedOf } from './companion.js';
|
|
11
13
|
import { CONFIG_PATH, DEFAULT_LAYOUT, KINDS, OVERRIDES_DIR, PROFILE_FILE, readConfig, RT_KIT_DIR } from './config.js';
|
|
12
14
|
import { hooksSection, SETTINGS_PATH } from './hooks-map.js';
|
|
@@ -17,6 +19,7 @@ import { pendingOf, planSync, runSync } from './sync.js';
|
|
|
17
19
|
import { answersRequirement, readTraits, unknownTraits } from './traits.js';
|
|
18
20
|
import { placeholdersOf } from './vars.js';
|
|
19
21
|
import { readAxes, unansweredAxes } from './variants.js';
|
|
22
|
+
import { byText } from './order.js';
|
|
20
23
|
/** Что с файлом сделала раскладка. Прошедшее время здесь правда: `sync` уже записал. */
|
|
21
24
|
const DONE_WORD = {
|
|
22
25
|
create: 'положен',
|
|
@@ -58,6 +61,7 @@ const KIND_TITLE = {
|
|
|
58
61
|
workflows: 'ВОРКФЛОУ',
|
|
59
62
|
templates: 'ШАБЛОНЫ',
|
|
60
63
|
docs: 'ДОКУМЕНТЫ',
|
|
64
|
+
samples: 'ОБРАЗЦЫ',
|
|
61
65
|
};
|
|
62
66
|
/** Состояние компаньона словами. У заполненного слова нет: о нём говорить нечего. */
|
|
63
67
|
const COMPANION_WORD = {
|
|
@@ -82,20 +86,9 @@ const PROFILE_HELPER = 'rt_needs';
|
|
|
82
86
|
* есть.
|
|
83
87
|
*/
|
|
84
88
|
const PROFILE_DEFINE = /(rt_[a-z_]+)\s*\(\)\s*\{/g;
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
* Отдельной строкой в разборе состояния потому, что на месте вызова нехватка не видна вовсе:
|
|
89
|
-
* хук выходит с нулём, стоит в настройке, виден в списке — и читается как работающий. Тишина
|
|
90
|
-
* при этом означает разом «нечего проверять», «проверять нечем» и «всё в порядке».
|
|
91
|
-
*/
|
|
92
|
-
function profileLines(root, assetsDir, config) {
|
|
93
|
-
const wanted = new Set();
|
|
94
|
-
const defined = new Set();
|
|
95
|
-
for (const asset of collectAssets(config, assetsDir)) {
|
|
96
|
-
if (asset.kind !== 'hooks') {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
89
|
+
/** Что взятые хуки зовут у профиля и что определяют сами. */
|
|
90
|
+
function readHooks(assets, wanted, defined) {
|
|
91
|
+
for (const asset of assets.filter((one) => one.kind === 'hooks')) {
|
|
99
92
|
for (const found of asset.text.matchAll(PROFILE_CALL)) {
|
|
100
93
|
if (found[1] !== PROFILE_HELPER) {
|
|
101
94
|
wanted.add(found[1]);
|
|
@@ -108,23 +101,36 @@ function profileLines(root, assetsDir, config) {
|
|
|
108
101
|
defined.add(found[1]);
|
|
109
102
|
}
|
|
110
103
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
}
|
|
105
|
+
/** Функции, определённые самим профилем: разложенным умолчанием пакета и надстройкой дерева. */
|
|
106
|
+
function readProfiles(root, config, defined) {
|
|
114
107
|
// Профиль собирается из тех же файлов, что читает сам хук: сперва разложенное умолчание
|
|
115
108
|
// пакета, поверх — надстройка дерева. Судить по одной надстройке значило бы объявить мёртвым
|
|
116
109
|
// каждый гард дерева, которое умолчаний не переписывало.
|
|
117
110
|
const defaults = config.layout.defaults ?? DEFAULT_LAYOUT.defaults;
|
|
118
111
|
const sources = [join(root, defaults, PROFILE_FILE), join(root, RT_KIT_DIR, PROFILE_FILE)];
|
|
119
|
-
for (const path of sources) {
|
|
120
|
-
if (!existsSync(path)) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
112
|
+
for (const path of sources.filter((one) => existsSync(one))) {
|
|
123
113
|
for (const found of readFileSync(path, 'utf8').matchAll(PROFILE_DEFINE)) {
|
|
124
114
|
defined.add(found[1]);
|
|
125
115
|
}
|
|
126
116
|
}
|
|
127
|
-
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Функции профиля, которых ждут взятые хуки, и те из них, что дерево не определило.
|
|
120
|
+
*
|
|
121
|
+
* Отдельной строкой в разборе состояния потому, что на месте вызова нехватка не видна вовсе:
|
|
122
|
+
* хук выходит с нулём, стоит в настройке, виден в списке — и читается как работающий. Тишина
|
|
123
|
+
* при этом означает разом «нечего проверять», «проверять нечем» и «всё в порядке».
|
|
124
|
+
*/
|
|
125
|
+
function profileLines(root, assetsDir, config) {
|
|
126
|
+
const wanted = new Set();
|
|
127
|
+
const defined = new Set();
|
|
128
|
+
readHooks([...collectAssets(config, assetsDir)], wanted, defined);
|
|
129
|
+
if (!wanted.size) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
readProfiles(root, config, defined);
|
|
133
|
+
const absent = [...wanted].filter((name) => !defined.has(name)).sort(byText);
|
|
128
134
|
return [
|
|
129
135
|
`функции профиля, которых ждут взятые хуки: ${wanted.size}`,
|
|
130
136
|
...(absent.length
|
|
@@ -135,7 +141,14 @@ function profileLines(root, assetsDir, config) {
|
|
|
135
141
|
: [' все определены']),
|
|
136
142
|
];
|
|
137
143
|
}
|
|
138
|
-
|
|
144
|
+
/** Дырка в тексте пакета, как она написана в самом тексте. */
|
|
145
|
+
function holeOf(name) {
|
|
146
|
+
return `{{${name}}}`;
|
|
147
|
+
}
|
|
148
|
+
const holes = (result) => [...result.missing].map(([asset, names]) => {
|
|
149
|
+
const written = names.map(holeOf).join(', ');
|
|
150
|
+
return ` ${asset}: нет значений для ${written}`;
|
|
151
|
+
});
|
|
139
152
|
const unfilled = (result) => result.companions.filter(isUnfilled);
|
|
140
153
|
/**
|
|
141
154
|
* Строка про компаньон, которому нечего сказать.
|
|
@@ -200,13 +213,34 @@ function strangeTraits(config, assetsDir) {
|
|
|
200
213
|
const unboundLines = (result) => result.unbound.length
|
|
201
214
|
? [
|
|
202
215
|
`гарды разложены, но в \`${SETTINGS_PATH}\` их не зовёт никто: ${result.unbound.length}`,
|
|
203
|
-
...result.unbound.map((binding) =>
|
|
216
|
+
...result.unbound.map((binding) => {
|
|
217
|
+
const matcher = binding.matcher ? ' ' + binding.matcher : '';
|
|
218
|
+
return ` ${binding.path} — ${binding.event}${matcher}`;
|
|
219
|
+
}),
|
|
204
220
|
` вставь в \`${SETTINGS_PATH}\` раздел \`hooks\` — готовый кусок ниже:`,
|
|
205
221
|
...JSON.stringify({ hooks: hooksSection(result.unbound) }, null, 4)
|
|
206
222
|
.split('\n')
|
|
207
223
|
.map((line) => ` ${line}`),
|
|
208
224
|
]
|
|
209
225
|
: [];
|
|
226
|
+
/**
|
|
227
|
+
* Гарды, подписанные не на то, что объявляют.
|
|
228
|
+
*
|
|
229
|
+
* Называются обе стороны: объявленное гардом и стоящее в настройке. Одной стороной такую строку
|
|
230
|
+
* не починить — правится настройка, а верное значение лежит в гарде, и по разнице видно, что
|
|
231
|
+
* именно до него не доходит.
|
|
232
|
+
*/
|
|
233
|
+
const driftedLines = (result) => result.drifted.length
|
|
234
|
+
? [
|
|
235
|
+
`гарды подписаны не на то, что объявляют: ${result.drifted.length}`,
|
|
236
|
+
...result.drifted.flatMap((drift) => [
|
|
237
|
+
` ${drift.path} — ${drift.event}`,
|
|
238
|
+
` объявлено: ${drift.declared || '(без образца)'}`,
|
|
239
|
+
` в настройке: ${drift.bound || '(без образца)'}`,
|
|
240
|
+
]),
|
|
241
|
+
` поправь образец в \`${SETTINGS_PATH}\` по объявлению гарда: тело его разбирает то, что объявлено`,
|
|
242
|
+
]
|
|
243
|
+
: [];
|
|
210
244
|
/**
|
|
211
245
|
* Разорванные связи между ресурсами.
|
|
212
246
|
*
|
|
@@ -230,7 +264,10 @@ const brokenLines = (result) => result.broken.length
|
|
|
230
264
|
const idleLines = (result) => result.idle.length
|
|
231
265
|
? [
|
|
232
266
|
`строк отказа, которые ничего не снимают: ${result.idle.length}`,
|
|
233
|
-
...result.idle.map((one) =>
|
|
267
|
+
...result.idle.map((one) => {
|
|
268
|
+
const why = one.by ? 'снято отказом от ' + one.by : 'нет в пакете';
|
|
269
|
+
return ` ${one.id} — ${why}`;
|
|
270
|
+
}),
|
|
234
271
|
' это предупреждение, а не отказ: строки убирает дерево, и раскладка идёт дальше',
|
|
235
272
|
]
|
|
236
273
|
: [];
|
|
@@ -293,6 +330,7 @@ const describe = (result) => [
|
|
|
293
330
|
...holes(result),
|
|
294
331
|
...gapLines(result),
|
|
295
332
|
...unboundLines(result),
|
|
333
|
+
...driftedLines(result),
|
|
296
334
|
...warnings(result),
|
|
297
335
|
...pendingOf(result).map((entry) => ` ${entry.path} — ${STATE_WORD[entry.outcome]}`),
|
|
298
336
|
...unfilled(result).map(companionLine),
|
|
@@ -310,7 +348,7 @@ function placeholdersIn(config, assetsDir) {
|
|
|
310
348
|
}
|
|
311
349
|
}
|
|
312
350
|
}
|
|
313
|
-
return names.sort();
|
|
351
|
+
return names.sort(byText);
|
|
314
352
|
}
|
|
315
353
|
/**
|
|
316
354
|
* Что проект выбрал при заведении конфига. Пустой список — весь набор; выбор приходит сюда
|
|
@@ -322,7 +360,7 @@ export function init(root, only = [], variants = {}, assetsDir = '') {
|
|
|
322
360
|
if (existsSync(path)) {
|
|
323
361
|
return { code: 0, lines: [`${CONFIG_PATH} уже есть — оставлен как есть`] };
|
|
324
362
|
}
|
|
325
|
-
const config = { vars: {}, layout: DEFAULT_LAYOUT,
|
|
363
|
+
const config = { vars: {}, layout: DEFAULT_LAYOUT, skip: [], variants, only };
|
|
326
364
|
mkdirSync(join(root, OVERRIDES_DIR), { recursive: true });
|
|
327
365
|
mkdirSync(join(root, CONFIG_PATH, '..'), { recursive: true });
|
|
328
366
|
writeFileSync(path, `${JSON.stringify(config, null, 4)}\n`, 'utf8');
|
|
@@ -380,30 +418,34 @@ const staleRefusal = (stale) => ({
|
|
|
380
418
|
' собери пакет и повтори',
|
|
381
419
|
],
|
|
382
420
|
});
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
421
|
+
/**
|
|
422
|
+
* Отказ, случившийся до первой записи на диск. Пусто — раскладке ничто не мешает.
|
|
423
|
+
*
|
|
424
|
+
* Ось без ответа отбивает раскладку целиком, а не пропускает свои ресурсы молча: правило
|
|
425
|
+
* поставки бывает в трёх видах, и дерево, не назвавшее свой, осталось бы вовсе без правила
|
|
426
|
+
* поставки — заметить это можно было бы только по тому, что гейт перестал его требовать.
|
|
427
|
+
*
|
|
428
|
+
* Незнакомое свойство — опечатка, и молчать о ней нельзя ни с одной стороны. У дерева она
|
|
429
|
+
* означает, что помеченного им ресурса оно не получит вовсе; у ресурса — что он не ляжет
|
|
430
|
+
* никуда и никогда, а причину в имени файла не разглядеть.
|
|
431
|
+
*/
|
|
432
|
+
function refusalBeforeSync(env, config) {
|
|
389
433
|
if (env.stale) {
|
|
390
434
|
return staleRefusal(env.stale);
|
|
391
435
|
}
|
|
392
|
-
|
|
393
|
-
// поставки бывает в трёх видах, и дерево, не назвавшее свой, осталось бы вовсе без правила
|
|
394
|
-
// поставки — заметить это можно было бы только по тому, что гейт перестал его требовать.
|
|
395
|
-
const unanswered = unansweredAxes(readAxes(assetsDir), config.variants);
|
|
436
|
+
const unanswered = unansweredAxes(readAxes(env.assetsDir), config.variants);
|
|
396
437
|
if (unanswered.length) {
|
|
397
438
|
return { code: 1, lines: ['раскладка не начата: не выбран вид', ...axisLines(unanswered)] };
|
|
398
439
|
}
|
|
399
|
-
|
|
400
|
-
// означает, что помеченного им ресурса оно не получит вовсе; у ресурса — что он не ляжет
|
|
401
|
-
// никуда и никогда, а причину в имени файла не разглядеть.
|
|
402
|
-
const strange = strangeTraits(config, assetsDir);
|
|
440
|
+
const strange = strangeTraits(config, env.assetsDir);
|
|
403
441
|
if (strange.length) {
|
|
404
442
|
return { code: 1, lines: ['раскладка не начата: свойство дерева не объявлено пакетом', ...strange] };
|
|
405
443
|
}
|
|
406
|
-
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
/** Ответ `sync --check`: расхождения считаются, на диск не пишется ничего. */
|
|
447
|
+
function syncCheck(config, root, version, assetsDir) {
|
|
448
|
+
{
|
|
407
449
|
const result = planSync(config, root, version, assetsDir);
|
|
408
450
|
const pending = pendingOf(result);
|
|
409
451
|
// Незаполненный компаньон — такое же расхождение, как отставший файл: правило разложено,
|
|
@@ -414,12 +456,30 @@ export function sync(env, check) {
|
|
|
414
456
|
// Разорванная связь в счёт расхождений не идёт: дерево вправе закрыть требование своим
|
|
415
457
|
// средством, и отказ отбивал бы законную раскладку. Но и сходство её не отменяет —
|
|
416
458
|
// предупреждение печатается и там, где расходиться больше нечему.
|
|
417
|
-
|
|
459
|
+
//
|
|
460
|
+
// Гард, подписанный не на то, что объявляет, идёт в счёт по той же причине и с большим
|
|
461
|
+
// основанием: неподключённый хотя бы не притворяется — этот выглядит работающим, и ветка
|
|
462
|
+
// его тела, ради которой всё писалось, не исполняется ни разу.
|
|
463
|
+
const count = result.missing.size + result.gaps.length + pending.length + empty.length + result.unbound.length + result.drifted.length;
|
|
418
464
|
if (!count) {
|
|
419
465
|
return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`, ...warnings(result)] };
|
|
420
466
|
}
|
|
421
467
|
return { code: 1, lines: [`sync --check: расхождений ${count}`, ...describe(result)] };
|
|
422
468
|
}
|
|
469
|
+
}
|
|
470
|
+
export function sync(env, check) {
|
|
471
|
+
const { root, version, assetsDir } = env;
|
|
472
|
+
const config = readConfig(root);
|
|
473
|
+
if (!config) {
|
|
474
|
+
return { code: 1, lines: [NO_CONFIG] };
|
|
475
|
+
}
|
|
476
|
+
const refusal = refusalBeforeSync(env, config);
|
|
477
|
+
if (refusal) {
|
|
478
|
+
return refusal;
|
|
479
|
+
}
|
|
480
|
+
if (check) {
|
|
481
|
+
return syncCheck(config, root, version, assetsDir);
|
|
482
|
+
}
|
|
423
483
|
const result = runSync(config, root, version, assetsDir);
|
|
424
484
|
// Перечисляются только дырки: раскладка не начата, и назвать файл «положенным» значило бы
|
|
425
485
|
// сказать неправду — на диске его нет.
|
|
@@ -456,6 +516,7 @@ export function sync(env, check) {
|
|
|
456
516
|
? [`разложено файлов: ${result.written.length}`, ...result.written.map((path) => ` ${path}`)]
|
|
457
517
|
: ['всё уже разложено']),
|
|
458
518
|
...unboundLines(result),
|
|
519
|
+
...driftedLines(result),
|
|
459
520
|
...debtLines(config, root, assetsDir),
|
|
460
521
|
...warnings(result),
|
|
461
522
|
],
|
|
@@ -524,6 +585,49 @@ const countLines = (counted) => {
|
|
|
524
585
|
const width = counted.reduce((found, entry) => Math.max(found, entry.name.length), 0);
|
|
525
586
|
return counted.map((entry) => ` ${entry.name.padEnd(width)} ${entry.count}`);
|
|
526
587
|
};
|
|
588
|
+
/** Сводка строками: загруженное, незагруженное, отбивки гейта и отказы гардов. */
|
|
589
|
+
function statsLines(summary, swept, days, version, laidOut) {
|
|
590
|
+
return [
|
|
591
|
+
`наблюдения за ${days} дн., заходов ${summary.sessions}, событий ${summary.total}`,
|
|
592
|
+
...(summary.versions.length ? [`версии пакета в записях: ${summary.versions.join(', ')}`] : []),
|
|
593
|
+
'',
|
|
594
|
+
`правил загружено: ${summary.loads.reduce((found, entry) => found + entry.count, 0)}`,
|
|
595
|
+
...countLines(summary.loads),
|
|
596
|
+
...(summary.unused.length
|
|
597
|
+
? [
|
|
598
|
+
'',
|
|
599
|
+
`разложено и не загружено ни разу: ${summary.unused.length} из ${laidOut}`,
|
|
600
|
+
...summary.unused.slice(0, UNUSED_SHOWN).map((name) => ` ${name}`),
|
|
601
|
+
// Список говорится не весь, и об этом говорится вслух: молчаливый обрыв
|
|
602
|
+
// читается как «вот они все», и правило, не попавшее в первую дюжину,
|
|
603
|
+
// считалось бы работающим.
|
|
604
|
+
...(summary.unused.length > UNUSED_SHOWN
|
|
605
|
+
? [` … и ещё ${summary.unused.length - UNUSED_SHOWN} — целиком в \`--json\``]
|
|
606
|
+
: []),
|
|
607
|
+
]
|
|
608
|
+
: []),
|
|
609
|
+
...(summary.denials.length
|
|
610
|
+
? [
|
|
611
|
+
'',
|
|
612
|
+
`гейт отбивал: ${summary.denials.reduce((found, entry) => found + entry.count, 0)}`,
|
|
613
|
+
...countLines(summary.denials),
|
|
614
|
+
...(summary.kinds.length
|
|
615
|
+
? [' чаще всего на:', ...countLines(summary.kinds).map((line) => ` ${line}`)]
|
|
616
|
+
: []),
|
|
617
|
+
]
|
|
618
|
+
: []),
|
|
619
|
+
...(summary.guards.length
|
|
620
|
+
? [
|
|
621
|
+
'',
|
|
622
|
+
`гарды отказывали: ${summary.guards.reduce((found, entry) => found + entry.count, 0)}`,
|
|
623
|
+
...countLines(summary.guards),
|
|
624
|
+
]
|
|
625
|
+
: []),
|
|
626
|
+
...(swept.length ? ['', `снято по сроку хранения (${KEEP_DAYS} дн.): ${swept.length}`] : []),
|
|
627
|
+
'',
|
|
628
|
+
`пакет v${version}`,
|
|
629
|
+
];
|
|
630
|
+
}
|
|
527
631
|
/**
|
|
528
632
|
* Сводка наблюдений за отрезок дней.
|
|
529
633
|
*
|
|
@@ -575,46 +679,7 @@ export function stats(env, options) {
|
|
|
575
679
|
}
|
|
576
680
|
return {
|
|
577
681
|
code: 0,
|
|
578
|
-
lines:
|
|
579
|
-
`наблюдения за ${days} дн., заходов ${summary.sessions}, событий ${summary.total}`,
|
|
580
|
-
...(summary.versions.length ? [`версии пакета в записях: ${summary.versions.join(', ')}`] : []),
|
|
581
|
-
'',
|
|
582
|
-
`правил загружено: ${summary.loads.reduce((found, entry) => found + entry.count, 0)}`,
|
|
583
|
-
...countLines(summary.loads),
|
|
584
|
-
...(summary.unused.length
|
|
585
|
-
? [
|
|
586
|
-
'',
|
|
587
|
-
`разложено и не загружено ни разу: ${summary.unused.length} из ${laidOutSkills(config, assetsDir).length}`,
|
|
588
|
-
...summary.unused.slice(0, UNUSED_SHOWN).map((name) => ` ${name}`),
|
|
589
|
-
// Список говорится не весь, и об этом говорится вслух: молчаливый обрыв
|
|
590
|
-
// читается как «вот они все», и правило, не попавшее в первую дюжину,
|
|
591
|
-
// считалось бы работающим.
|
|
592
|
-
...(summary.unused.length > UNUSED_SHOWN
|
|
593
|
-
? [` … и ещё ${summary.unused.length - UNUSED_SHOWN} — целиком в \`--json\``]
|
|
594
|
-
: []),
|
|
595
|
-
]
|
|
596
|
-
: []),
|
|
597
|
-
...(summary.denials.length
|
|
598
|
-
? [
|
|
599
|
-
'',
|
|
600
|
-
`гейт отбивал: ${summary.denials.reduce((found, entry) => found + entry.count, 0)}`,
|
|
601
|
-
...countLines(summary.denials),
|
|
602
|
-
...(summary.kinds.length
|
|
603
|
-
? [' чаще всего на:', ...countLines(summary.kinds).map((line) => ` ${line}`)]
|
|
604
|
-
: []),
|
|
605
|
-
]
|
|
606
|
-
: []),
|
|
607
|
-
...(summary.guards.length
|
|
608
|
-
? [
|
|
609
|
-
'',
|
|
610
|
-
`гарды отказывали: ${summary.guards.reduce((found, entry) => found + entry.count, 0)}`,
|
|
611
|
-
...countLines(summary.guards),
|
|
612
|
-
]
|
|
613
|
-
: []),
|
|
614
|
-
...(result.swept.length ? ['', `снято по сроку хранения (${KEEP_DAYS} дн.): ${result.swept.length}`] : []),
|
|
615
|
-
'',
|
|
616
|
-
`пакет v${version}`,
|
|
617
|
-
],
|
|
682
|
+
lines: statsLines(summary, result.swept, days, version, laidOutSkills(config, assetsDir).length),
|
|
618
683
|
};
|
|
619
684
|
}
|
|
620
685
|
export function doctor(env) {
|
|
@@ -658,7 +723,10 @@ export function doctor(env) {
|
|
|
658
723
|
`не выбрано: ${catalog.length - taken - skipped - other - cut.size - needsTrait}, пропущено: ${skipped}, другой вид: ${other}, снято каскадом: ${cut.size}, нужно свойство: ${needsTrait}`,
|
|
659
724
|
...unchosen.map((entry) => ` не выбран: ${entry.id}`),
|
|
660
725
|
...needing.map((entry) => ` нужно свойство «${entry.needs}»: ${entry.id}`),
|
|
661
|
-
...cuts.map((one) =>
|
|
726
|
+
...cuts.map((one) => {
|
|
727
|
+
const root = one.parent === one.root ? '' : ', отвергнут ' + one.root;
|
|
728
|
+
return ` снят каскадом: ${one.id} — вслед за ${one.parent}${root}`;
|
|
729
|
+
}),
|
|
662
730
|
...profileLines(root, assetsDir, config),
|
|
663
731
|
`значений в конфиге: ${Object.keys(config.vars).length}`,
|
|
664
732
|
...chosen,
|