@rt-tools/agent-kit 0.1.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 +85 -0
- package/assets/laws/access.md +46 -0
- package/assets/laws/admin-lists.md +47 -0
- package/assets/laws/admin-navigation.md +50 -0
- package/assets/laws/code-structure.md +32 -0
- package/assets/laws/delivery.md +34 -0
- package/assets/laws/entity-editing.md +73 -0
- package/assets/laws/entity-models.md +34 -0
- package/assets/laws/frontend-application.md +34 -0
- package/assets/laws/lib-imports.md +31 -0
- package/assets/laws/locales.md +44 -0
- package/assets/laws/project-documentation.md +39 -0
- package/assets/laws/reuse-first.md +35 -0
- package/assets/laws/search-visibility.md +37 -0
- package/assets/laws/shared-code.md +32 -0
- package/assets/laws/verifiability.md +33 -0
- package/assets/templates/pattern.md +29 -0
- package/assets/templates/rule.md +43 -0
- package/bin/agent-kit.d.ts +4 -0
- package/bin/agent-kit.d.ts.map +1 -0
- package/bin/agent-kit.js +56 -0
- package/bin/agent-kit.js.map +1 -0
- package/index.d.ts +9 -0
- package/index.d.ts.map +1 -0
- package/index.js +9 -0
- package/index.js.map +1 -0
- package/lib/assets.d.ts +11 -0
- package/lib/assets.d.ts.map +1 -0
- package/lib/assets.js +35 -0
- package/lib/assets.js.map +1 -0
- package/lib/commands.d.ts +14 -0
- package/lib/commands.d.ts.map +1 -0
- package/lib/commands.js +100 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +25 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +85 -0
- package/lib/config.js.map +1 -0
- package/lib/package-root.d.ts +2 -0
- package/lib/package-root.d.ts.map +1 -0
- package/lib/package-root.js +27 -0
- package/lib/package-root.js.map +1 -0
- package/lib/plan.d.ts +33 -0
- package/lib/plan.d.ts.map +1 -0
- package/lib/plan.js +36 -0
- package/lib/plan.js.map +1 -0
- package/lib/sections.d.ts +38 -0
- package/lib/sections.d.ts.map +1 -0
- package/lib/sections.js +93 -0
- package/lib/sections.js.map +1 -0
- package/lib/stamp.d.ts +22 -0
- package/lib/stamp.d.ts.map +1 -0
- package/lib/stamp.js +57 -0
- package/lib/stamp.js.map +1 -0
- package/lib/sync.d.ts +13 -0
- package/lib/sync.d.ts.map +1 -0
- package/lib/sync.js +59 -0
- package/lib/sync.js.map +1 -0
- package/lib/vars.d.ts +20 -0
- package/lib/vars.d.ts.map +1 -0
- package/lib/vars.js +38 -0
- package/lib/vars.js.map +1 -0
- package/package.json +42 -0
- package/rt-tools-agent-kit-0.1.0.tgz +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Видимость в поиске
|
|
2
|
+
|
|
3
|
+
Как публичный сайт выглядит для поисковика. Страница живёт в нескольких языках под одним
|
|
4
|
+
доменом, и от разметки зависит, считает поисковик эти адреса одной страницей на разных
|
|
5
|
+
языках или дублями друг друга. Ошибка здесь не видна ни в сборке, ни в браузере: она
|
|
6
|
+
проявляется падением выдачи через недели.
|
|
7
|
+
|
|
8
|
+
**Ревизия:** 2026-08-05
|
|
9
|
+
|
|
10
|
+
## Статьи
|
|
11
|
+
|
|
12
|
+
- **У страницы один канонический адрес — её собственный, на её языке.** Общий канонический
|
|
13
|
+
адрес на все языки означает, что остальные версии объявлены копиями и в выдачу не попадут.
|
|
14
|
+
- **Разметка страницы при переходе переписывается, а не накапливается.** Иначе на второй
|
|
15
|
+
открытой странице остаётся описание первой, и два адреса объявляют один канонический.
|
|
16
|
+
- **Языковая версия объявляется поисковику только тогда, когда её перевод готов.** Позвать
|
|
17
|
+
поисковика на страницу, где текст остался на чужом языке, — значит отдать дубль под
|
|
18
|
+
неверным языковым тегом.
|
|
19
|
+
- **Страница объявляет себя структурированными данными.** Без них поисковик разбирает
|
|
20
|
+
содержимое догадками.
|
|
21
|
+
- **Карта сайта строится из живых данных, а не из файла.** Файл устаревает молча в тот же
|
|
22
|
+
день, когда добавили страницу.
|
|
23
|
+
- **Прежний адрес страницы ведёт на новый постоянным перенаправлением.** Смена адреса без
|
|
24
|
+
перенаправления обнуляет накопленный вес страницы.
|
|
25
|
+
- **Перенаправление с прежнего адреса живёт в кэше наравне со страницами.** Некэшируемое
|
|
26
|
+
перенаправление не вытесняет из кэша старую страницу, и гость видит её ещё сутки.
|
|
27
|
+
|
|
28
|
+
## Открытые вопросы
|
|
29
|
+
|
|
30
|
+
- **Q-SV-1 — что становится каноническим адресом, когда страниц одного вида несколько.**
|
|
31
|
+
Сегодня корень языка отдаёт единственную страницу и объявляет каноническим её адрес.
|
|
32
|
+
Решение изменит, появится ли у списка собственный канонический адрес. Заведён 2026-08-05.
|
|
33
|
+
|
|
34
|
+
## История изменений
|
|
35
|
+
|
|
36
|
+
- **2026-08-05.** Закон заведён: статьи собраны из правила `seo` — в нём осталось только то,
|
|
37
|
+
из чего разметка собирается.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Общий код приложений
|
|
2
|
+
|
|
3
|
+
Что сайт, админка и бэкенд понимают одинаково: размер страницы, набор операторов условия,
|
|
4
|
+
направление порядка, длину поля. Записанное дважды расходится по одному значению за раз, и
|
|
5
|
+
каждая копия при этом сама по себе исправна — ни линт, ни сборка, ни тесты второй не видят.
|
|
6
|
+
|
|
7
|
+
Закон говорит о том, что общим быть обязано. Где такой символ лежит и кто его видит — не
|
|
8
|
+
его предмет.
|
|
9
|
+
|
|
10
|
+
**Ревизия:** 2026-08-02
|
|
11
|
+
|
|
12
|
+
## Статьи
|
|
13
|
+
|
|
14
|
+
- **Число-настройка объявлено один раз на все приложения.** Размер страницы жил шестью
|
|
15
|
+
объявлениями, и одно из них успело стать `25` против `20` у остальных.
|
|
16
|
+
- **Набор значений, уже объявленный в общем пакете, заново не объявляется.** Оператор условия
|
|
17
|
+
и направление порядка лежали на бэкенде рукописной копией того же набора.
|
|
18
|
+
- **Значение из конечного набора сверяется с набором, а не приводится к типу.** Приведение
|
|
19
|
+
принимает любую строку, и порядок молча становится нечитаемым.
|
|
20
|
+
- **Номер страницы меньше единицы читается как первая.** Отрицательный номер доходил до
|
|
21
|
+
переключателя страниц как есть.
|
|
22
|
+
|
|
23
|
+
## Открытые вопросы
|
|
24
|
+
|
|
25
|
+
- **Q-S-1 — перевод сущности на двух сторонах устроен по-разному.** Фронт переводит классом
|
|
26
|
+
с приведением через `typeCast`, бэкенд — свободными функциями без общей основы, хотя пакет
|
|
27
|
+
с этой основой виден обоим. Решение изменит, станет ли `BaseMapper` общей формой перевода
|
|
28
|
+
или у сторон останутся две. Заведён 2026-08-02.
|
|
29
|
+
- **Q-S-2 — совпадение строковой настройки и таблицы соответствий не сверяется.** Проверка
|
|
30
|
+
считает только числа: у строк под одним именем значение бывает своим в каждой либе, и учёт
|
|
31
|
+
их дал бы полсотни ложных срабатываний. Решение изменит, появится ли признак, который
|
|
32
|
+
отличает общее понятие от совпавшего имени. Заведён 2026-08-02.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Проверяемость
|
|
2
|
+
|
|
3
|
+
Что считается подтверждением того, что работа сделана. Закон нужен потому, что пересказ
|
|
4
|
+
выглядит так же уверенно, как проверенное утверждение, и отличить их потом нечем: код,
|
|
5
|
+
который «должен работать», и код, который проверили, в истории выглядят одинаково.
|
|
6
|
+
|
|
7
|
+
**Ревизия:** 2026-08-05
|
|
8
|
+
|
|
9
|
+
## Статьи
|
|
10
|
+
|
|
11
|
+
- **Обещанное поведение названо сценарием, а сценарий назван в тесте, который его
|
|
12
|
+
проверяет.** Иначе связь между обещанием и проверкой держится только на памяти автора.
|
|
13
|
+
- **Сценарий без теста помечен как непокрытый и виден в сводке.** Молчаливый пропуск
|
|
14
|
+
выглядит так же, как покрытый сценарий, и долг перестаёт быть заметным.
|
|
15
|
+
- **Тест, проверяющий часть обещанного, считается долгом, а не покрытием.** Иначе сводка
|
|
16
|
+
показывает выполненным то, что выполнено наполовину.
|
|
17
|
+
- **Упоминание в тесте сценария, которого нет, — отказ.** Так ловится переименованный или
|
|
18
|
+
выкинутый сценарий: без этого он пропадает молча.
|
|
19
|
+
- **Работающее приложение проверяется там, где его видит пользователь.** Отладочный режим
|
|
20
|
+
ведёт себя иначе рабочего, и проверка в нём подтверждает не то, что будет у пользователя.
|
|
21
|
+
- **Разметка проверяется замером, а не взглядом.** На глаз не отличить отступ от поля и
|
|
22
|
+
выравнивание от совпадения, и расхождение находится уже после выкатки.
|
|
23
|
+
|
|
24
|
+
## Открытые вопросы
|
|
25
|
+
|
|
26
|
+
- **Q-V-1 — что считать достаточным покрытием сценария.** Сегодня достаточно одного теста,
|
|
27
|
+
назвавшего сценарий, и его глубину не проверяет ничто. Решение изменит, появится ли
|
|
28
|
+
требование к тому, каким путём тест идёт. Заведён 2026-08-05.
|
|
29
|
+
|
|
30
|
+
## История изменений
|
|
31
|
+
|
|
32
|
+
- **2026-08-05.** Закон заведён: статьи собраны из правил `testing` и `browser-verification`
|
|
33
|
+
— в них осталось то, из чего проверка собирается.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: <имя-правила>-<что>
|
|
3
|
+
kind: pattern
|
|
4
|
+
rule: <имя-правила>
|
|
5
|
+
description: Паттерн правила <имя-правила>. Брать <когда> — <что даёт>. Не брать для <соседний случай> — это паттерн <имя соседа>.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# <Что собирается>
|
|
9
|
+
|
|
10
|
+
Паттерн правила `<имя-правила>`. Что при этом должно быть верно — закон
|
|
11
|
+
`{{lawsDir}}/<закон>.md`.
|
|
12
|
+
|
|
13
|
+
## Когда брать
|
|
14
|
+
|
|
15
|
+
- <Случай, по которому паттерн узнаётся.>
|
|
16
|
+
|
|
17
|
+
## <Приём>
|
|
18
|
+
|
|
19
|
+
<Готовый код целиком, а не пересказ: паттерн берут, чтобы не писать заново.>
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
<код>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
<Одно-два предложения о том, почему именно так, если из кода этого не видно.>
|
|
26
|
+
|
|
27
|
+
## Частые промахи
|
|
28
|
+
|
|
29
|
+
- <Что делают вместо этого и чем это кончается.>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: <имя-правила>
|
|
3
|
+
kind: rule
|
|
4
|
+
law: <закон>
|
|
5
|
+
description: Правило проекта под закон «<Название закона>». Брать при <когда> — <что называет>. Готовый код — в паттернах <имена>.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# <О чём закон> — как это устроено здесь
|
|
9
|
+
|
|
10
|
+
Правило под закон `{{lawsDir}}/<закон>.md`. Закон говорит, что должно быть верно; здесь — чем
|
|
11
|
+
это названо в этом дереве, где лежит и что из закона у нас не применяется.
|
|
12
|
+
|
|
13
|
+
## Как это называется здесь
|
|
14
|
+
|
|
15
|
+
| В законе | Здесь |
|
|
16
|
+
| ---------------- | ----------------- |
|
|
17
|
+
| <понятие закона> | <имя в этом коде> |
|
|
18
|
+
|
|
19
|
+
## Где это лежит
|
|
20
|
+
|
|
21
|
+
| Что | Где |
|
|
22
|
+
| ---------- | -------- |
|
|
23
|
+
| <механизм> | `<путь>` |
|
|
24
|
+
|
|
25
|
+
## Как закон применяется здесь
|
|
26
|
+
|
|
27
|
+
Каждый пункт начинается жирной фразой, и у каждой жирной фразы есть строка в
|
|
28
|
+
`implementation.md` рядом. Утверждение, которому места в коде не нашлось, сюда не ставится: оно
|
|
29
|
+
уходит прозой в «Ловушки» или вопросом `Q-N` в закон.
|
|
30
|
+
|
|
31
|
+
- **<Утверждение одной фразой.>** <Что сломается иначе — не больше двух предложений.>
|
|
32
|
+
|
|
33
|
+
## Чего из закона здесь нет
|
|
34
|
+
|
|
35
|
+
<Что из закона этот проект не применяет и почему; ссылка на открытый вопрос закона.>
|
|
36
|
+
|
|
37
|
+
## Паттерны
|
|
38
|
+
|
|
39
|
+
- `<имя-правила>-<что>` — <когда брать>.
|
|
40
|
+
|
|
41
|
+
## Ловушки
|
|
42
|
+
|
|
43
|
+
- **<Грабли, на которые уже наступали.>** <Чем это кончилось.>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-kit.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/bin/agent-kit.ts"],"names":[],"mappings":";AAUA,OAAO,EAA8B,iBAAiB,EAAQ,MAAM,oBAAoB,CAAC;AAgCzF,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,CAc/D"}
|
package/bin/agent-kit.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Точка входа. Разбирает аргументы, зовёт команду и печатает то, что она вернула, — своей
|
|
4
|
+
* работы у неё нет: всё, что решает, живёт в `commands.ts` и проверяется спеками.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import process from 'node:process';
|
|
10
|
+
import { doctor, init, sync } from '../lib/commands.js';
|
|
11
|
+
import { packageRootFrom } from '../lib/package-root.js';
|
|
12
|
+
const USAGE = [
|
|
13
|
+
'agent-kit <команда>',
|
|
14
|
+
'',
|
|
15
|
+
' init завести .claude/rt-kit.json и каталог надстроек',
|
|
16
|
+
' sync разложить ресурсы пакета в дерево проекта',
|
|
17
|
+
' sync --check ничего не писать, отказать при расхождении — для гейта пуша',
|
|
18
|
+
' doctor рассказать о состоянии раскладки, ничего не меняя',
|
|
19
|
+
'',
|
|
20
|
+
' --root <путь> корень проекта; по умолчанию текущий каталог',
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Единственное место, где пакет узнаёт собственное расположение. `import.meta` живёт только
|
|
24
|
+
* здесь: библиотечные модули получают каталог ресурсов входным значением, и трансформ тестов,
|
|
25
|
+
* разбирающий модули как CommonJS, на них уже не спотыкается.
|
|
26
|
+
*/
|
|
27
|
+
function environmentOf(root) {
|
|
28
|
+
const pkg = packageRootFrom(dirname(fileURLToPath(import.meta.url)));
|
|
29
|
+
const version = JSON.parse(readFileSync(join(pkg, 'package.json'), 'utf8')).version;
|
|
30
|
+
return { root, version, assetsDir: join(pkg, 'assets') };
|
|
31
|
+
}
|
|
32
|
+
function optionOf(argv, name, fallback) {
|
|
33
|
+
const index = argv.indexOf(name);
|
|
34
|
+
return index >= 0 && argv[index + 1] ? argv[index + 1] : fallback;
|
|
35
|
+
}
|
|
36
|
+
export function main(argv) {
|
|
37
|
+
const command = argv[0] ?? '';
|
|
38
|
+
const env = environmentOf(resolve(optionOf(argv, '--root', process.cwd())));
|
|
39
|
+
switch (command) {
|
|
40
|
+
case 'init':
|
|
41
|
+
return init(env.root);
|
|
42
|
+
case 'sync':
|
|
43
|
+
return sync(env, argv.includes('--check'));
|
|
44
|
+
case 'doctor':
|
|
45
|
+
return doctor(env);
|
|
46
|
+
default:
|
|
47
|
+
return { code: command ? 1 : 0, lines: command ? [`неизвестная команда «${command}»`, '', ...USAGE] : USAGE };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const outcome = main(process.argv.slice(2));
|
|
51
|
+
for (const line of outcome.lines) {
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.log(line);
|
|
54
|
+
}
|
|
55
|
+
process.exit(outcome.code);
|
|
56
|
+
//# sourceMappingURL=agent-kit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-kit.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/bin/agent-kit.ts"],"names":[],"mappings":";AACA;;;GAGG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,EAAE,MAAM,EAAgB,IAAI,EAAqB,IAAI,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,KAAK,GAAsB;IAC7B,qBAAqB;IACrB,EAAE;IACF,mEAAmE;IACnE,6DAA6D;IAC7D,+EAA+E;IAC/E,qEAAqE;IACrE,EAAE;IACF,gEAAgE;CACnE,CAAC;AAEF;;;;GAIG;AACH,SAAS,aAAa,CAAC,IAAY;IAC/B,MAAM,GAAG,GAAW,eAAe,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAyB,CAAC,OAAO,CAAC;IAErH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED,SAAS,QAAQ,CAAC,IAAuB,EAAE,IAAY,EAAE,QAAgB;IACrE,MAAM,KAAK,GAAW,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC,OAAO,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,IAAuB;IACxC,MAAM,OAAO,GAAW,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,MAAM,GAAG,GAAiB,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IAE1F,QAAQ,OAAO,EAAE,CAAC;QACd,KAAK,MAAM;YACP,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1B,KAAK,MAAM;YACP,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QAC/C,KAAK,QAAQ;YACT,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB;YACI,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,wBAAwB,OAAO,GAAG,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;IACtH,CAAC;AACL,CAAC;AAED,MAAM,OAAO,GAAsB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IAC/B,sCAAsC;IACtC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AACD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\n/**\n * Точка входа. Разбирает аргументы, зовёт команду и печатает то, что она вернула, — своей\n * работы у неё нет: всё, что решает, живёт в `commands.ts` и проверяется спеками.\n */\nimport { readFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport process from 'node:process';\n\nimport { doctor, IEnvironment, init, IOutcomeOfCommand, sync } from '../lib/commands.js';\nimport { packageRootFrom } from '../lib/package-root.js';\n\nconst USAGE: readonly string[] = [\n 'agent-kit <команда>',\n '',\n ' init завести .claude/rt-kit.json и каталог надстроек',\n ' sync разложить ресурсы пакета в дерево проекта',\n ' sync --check ничего не писать, отказать при расхождении — для гейта пуша',\n ' doctor рассказать о состоянии раскладки, ничего не меняя',\n '',\n ' --root <путь> корень проекта; по умолчанию текущий каталог',\n];\n\n/**\n * Единственное место, где пакет узнаёт собственное расположение. `import.meta` живёт только\n * здесь: библиотечные модули получают каталог ресурсов входным значением, и трансформ тестов,\n * разбирающий модули как CommonJS, на них уже не спотыкается.\n */\nfunction environmentOf(root: string): IEnvironment {\n const pkg: string = packageRootFrom(dirname(fileURLToPath(import.meta.url)));\n const version: string = (JSON.parse(readFileSync(join(pkg, 'package.json'), 'utf8')) as { version: string }).version;\n\n return { root, version, assetsDir: join(pkg, 'assets') };\n}\n\nfunction optionOf(argv: readonly string[], name: string, fallback: string): string {\n const index: number = argv.indexOf(name);\n\n return index >= 0 && argv[index + 1] ? argv[index + 1] : fallback;\n}\n\nexport function main(argv: readonly string[]): IOutcomeOfCommand {\n const command: string = argv[0] ?? '';\n const env: IEnvironment = environmentOf(resolve(optionOf(argv, '--root', process.cwd())));\n\n switch (command) {\n case 'init':\n return init(env.root);\n case 'sync':\n return sync(env, argv.includes('--check'));\n case 'doctor':\n return doctor(env);\n default:\n return { code: command ? 1 : 0, lines: command ? [`неизвестная команда «${command}»`, '', ...USAGE] : USAGE };\n }\n}\n\nconst outcome: IOutcomeOfCommand = main(process.argv.slice(2));\nfor (const line of outcome.lines) {\n // eslint-disable-next-line no-console\n console.log(line);\n}\nprocess.exit(outcome.code);\n"]}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './lib/assets.js';
|
|
2
|
+
export * from './lib/commands.js';
|
|
3
|
+
export * from './lib/config.js';
|
|
4
|
+
export * from './lib/plan.js';
|
|
5
|
+
export * from './lib/sections.js';
|
|
6
|
+
export * from './lib/stamp.js';
|
|
7
|
+
export * from './lib/sync.js';
|
|
8
|
+
export * from './lib/vars.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../projects/agent-kit/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}
|
package/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './lib/assets.js';
|
|
2
|
+
export * from './lib/commands.js';
|
|
3
|
+
export * from './lib/config.js';
|
|
4
|
+
export * from './lib/plan.js';
|
|
5
|
+
export * from './lib/sections.js';
|
|
6
|
+
export * from './lib/stamp.js';
|
|
7
|
+
export * from './lib/sync.js';
|
|
8
|
+
export * from './lib/vars.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
package/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../projects/agent-kit/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC","sourcesContent":["export * from './lib/assets.js';\nexport * from './lib/commands.js';\nexport * from './lib/config.js';\nexport * from './lib/plan.js';\nexport * from './lib/sections.js';\nexport * from './lib/stamp.js';\nexport * from './lib/sync.js';\nexport * from './lib/vars.js';\n"]}
|
package/lib/assets.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IConfig, TKind } from './config.js';
|
|
2
|
+
export interface IAsset {
|
|
3
|
+
/** Идентификатор: `laws/delivery.md`. Он же путь надстройки и ключ в `skip`. */
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly kind: TKind;
|
|
6
|
+
readonly text: string;
|
|
7
|
+
/** Путь в дереве проекта, куда ресурс ложится. */
|
|
8
|
+
readonly target: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function collectAssets(config: IConfig, root: string): readonly IAsset[];
|
|
11
|
+
//# sourceMappingURL=assets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,OAAO,EAAS,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,WAAW,MAAM;IACnB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B;AAaD,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAa9E"}
|
package/lib/assets.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Что пакет везёт и куда это ложится.
|
|
3
|
+
*
|
|
4
|
+
* Список ресурсов не выписан руками, а читается из каталога `assets/`: выписанный список
|
|
5
|
+
* разошёлся бы с каталогом на первом же добавленном законе, и заметить это было бы нечем —
|
|
6
|
+
* ресурс просто не приезжал бы к проекту.
|
|
7
|
+
*/
|
|
8
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { KINDS } from './config.js';
|
|
11
|
+
function filesOf(dir) {
|
|
12
|
+
try {
|
|
13
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
14
|
+
.filter((entry) => entry.isFile())
|
|
15
|
+
.map((entry) => entry.name)
|
|
16
|
+
.sort();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function collectAssets(config, root) {
|
|
23
|
+
const assets = [];
|
|
24
|
+
for (const kind of KINDS) {
|
|
25
|
+
for (const name of filesOf(join(root, kind))) {
|
|
26
|
+
const id = `${kind}/${name}`;
|
|
27
|
+
if (config.skip.includes(id)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
assets.push({ id, kind, text: readFileSync(join(root, kind, name), 'utf8'), target: join(config.layout[kind], name) });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return assets;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAU,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAW,KAAK,EAAS,MAAM,aAAa,CAAC;AAWpD,SAAS,OAAO,CAAC,GAAW;IACxB,IAAI,CAAC;QACD,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aAC3C,MAAM,CAAC,CAAC,KAAa,EAAW,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;aAClD,GAAG,CAAC,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;aAC1C,IAAI,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAe,EAAE,IAAY;IACvD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;YAC3C,MAAM,EAAE,GAAW,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;YACrC,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBAC3B,SAAS;YACb,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3H,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Что пакет везёт и куда это ложится.\n *\n * Список ресурсов не выписан руками, а читается из каталога `assets/`: выписанный список\n * разошёлся бы с каталогом на первом же добавленном законе, и заметить это было бы нечем —\n * ресурс просто не приезжал бы к проекту.\n */\nimport { Dirent, readdirSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { IConfig, KINDS, TKind } from './config.js';\n\nexport interface IAsset {\n /** Идентификатор: `laws/delivery.md`. Он же путь надстройки и ключ в `skip`. */\n readonly id: string;\n readonly kind: TKind;\n readonly text: string;\n /** Путь в дереве проекта, куда ресурс ложится. */\n readonly target: string;\n}\n\nfunction filesOf(dir: string): readonly string[] {\n try {\n return readdirSync(dir, { withFileTypes: true })\n .filter((entry: Dirent): boolean => entry.isFile())\n .map((entry: Dirent): string => entry.name)\n .sort();\n } catch {\n return [];\n }\n}\n\nexport function collectAssets(config: IConfig, root: string): readonly IAsset[] {\n const assets: IAsset[] = [];\n for (const kind of KINDS) {\n for (const name of filesOf(join(root, kind))) {\n const id: string = `${kind}/${name}`;\n if (config.skip.includes(id)) {\n continue;\n }\n assets.push({ id, kind, text: readFileSync(join(root, kind, name), 'utf8'), target: join(config.layout[kind], name) });\n }\n }\n\n return assets;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface IOutcomeOfCommand {
|
|
2
|
+
readonly code: number;
|
|
3
|
+
readonly lines: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
/** Всё, что команда знает о мире: корень проекта, версия пакета и его каталог ресурсов. */
|
|
6
|
+
export interface IEnvironment {
|
|
7
|
+
readonly root: string;
|
|
8
|
+
readonly version: string;
|
|
9
|
+
readonly assetsDir: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function init(root: string): IOutcomeOfCommand;
|
|
12
|
+
export declare function sync(env: IEnvironment, check: boolean): IOutcomeOfCommand;
|
|
13
|
+
export declare function doctor(env: IEnvironment): IOutcomeOfCommand;
|
|
14
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/commands.ts"],"names":[],"mappings":"AAaA,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;CAC9B;AAuBD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAmBpD;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,iBAAiB,CA0CzE;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAqB3D"}
|
package/lib/commands.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Три команды и их вывод. Печать отделена от работы: команда возвращает строки и код возврата,
|
|
3
|
+
* а `process.exit` зовёт только точка входа — иначе ни одну из них нельзя было бы проверить
|
|
4
|
+
* спекой, не перехватывая поток вывода.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { collectAssets } from './assets.js';
|
|
9
|
+
import { CONFIG_PATH, DEFAULT_LAYOUT, OVERRIDES_DIR, readConfig } from './config.js';
|
|
10
|
+
import { isRefusal } from './plan.js';
|
|
11
|
+
import { pendingOf, planSync, runSync } from './sync.js';
|
|
12
|
+
const WORD = {
|
|
13
|
+
create: 'положен',
|
|
14
|
+
update: 'переложен',
|
|
15
|
+
drift: 'правлен руками',
|
|
16
|
+
foreign: 'положен не пакетом',
|
|
17
|
+
ok: 'без изменений',
|
|
18
|
+
};
|
|
19
|
+
const NO_CONFIG = `нет \`${CONFIG_PATH}\` — начни с \`agent-kit init\``;
|
|
20
|
+
const holes = (result) => [...result.missing].map(([asset, names]) => ` ${asset}: нет значений для ${names.map((name) => `{{${name}}}`).join(', ')}`);
|
|
21
|
+
const describe = (result) => [
|
|
22
|
+
...holes(result),
|
|
23
|
+
...pendingOf(result).map((entry) => ` ${entry.path} — ${WORD[entry.outcome]}`),
|
|
24
|
+
];
|
|
25
|
+
export function init(root) {
|
|
26
|
+
const path = join(root, CONFIG_PATH);
|
|
27
|
+
if (existsSync(path)) {
|
|
28
|
+
return { code: 0, lines: [`${CONFIG_PATH} уже есть — оставлен как есть`] };
|
|
29
|
+
}
|
|
30
|
+
const config = { vars: {}, layout: DEFAULT_LAYOUT, skip: [] };
|
|
31
|
+
mkdirSync(join(root, OVERRIDES_DIR), { recursive: true });
|
|
32
|
+
mkdirSync(join(root, CONFIG_PATH, '..'), { recursive: true });
|
|
33
|
+
writeFileSync(path, `${JSON.stringify(config, null, 4)}\n`, 'utf8');
|
|
34
|
+
return {
|
|
35
|
+
code: 0,
|
|
36
|
+
lines: [
|
|
37
|
+
`заведён ${CONFIG_PATH}`,
|
|
38
|
+
`заведён ${OVERRIDES_DIR}/ — надстройки проекта кладутся сюда путём ресурса`,
|
|
39
|
+
'дальше: заполни `vars` и запусти `agent-kit sync`',
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function sync(env, check) {
|
|
44
|
+
const { root, version, assetsDir } = env;
|
|
45
|
+
const config = readConfig(root);
|
|
46
|
+
if (!config) {
|
|
47
|
+
return { code: 1, lines: [NO_CONFIG] };
|
|
48
|
+
}
|
|
49
|
+
if (check) {
|
|
50
|
+
const result = planSync(config, root, version, assetsDir);
|
|
51
|
+
const pending = pendingOf(result);
|
|
52
|
+
if (!result.missing.size && !pending.length) {
|
|
53
|
+
return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`] };
|
|
54
|
+
}
|
|
55
|
+
return { code: 1, lines: [`sync --check: расхождений ${result.missing.size + pending.length}`, ...describe(result)] };
|
|
56
|
+
}
|
|
57
|
+
const result = runSync(config, root, version, assetsDir);
|
|
58
|
+
// Перечисляются только дырки: раскладка не начата, и назвать файл «положенным» значило бы
|
|
59
|
+
// сказать неправду — на диске его нет.
|
|
60
|
+
if (result.missing.size) {
|
|
61
|
+
return { code: 1, lines: ['раскладка не начата: нечего подставить в дырки', ...holes(result)] };
|
|
62
|
+
}
|
|
63
|
+
const refused = result.planned.filter((entry) => isRefusal(entry.outcome));
|
|
64
|
+
if (refused.length) {
|
|
65
|
+
return {
|
|
66
|
+
code: 1,
|
|
67
|
+
lines: [
|
|
68
|
+
'раскладка не начата: эти файлы пакет переписывать не станет',
|
|
69
|
+
...refused.map((entry) => ` ${entry.path} — ${WORD[entry.outcome]}`),
|
|
70
|
+
'правку надо либо перенести в надстройку, либо снять — и повторить',
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
code: 0,
|
|
76
|
+
lines: result.written.length
|
|
77
|
+
? [`разложено файлов: ${result.written.length}`, ...result.written.map((path) => ` ${path}`)]
|
|
78
|
+
: ['всё уже разложено'],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function doctor(env) {
|
|
82
|
+
const { root, version, assetsDir } = env;
|
|
83
|
+
const config = readConfig(root);
|
|
84
|
+
if (!config) {
|
|
85
|
+
return { code: 1, lines: [NO_CONFIG] };
|
|
86
|
+
}
|
|
87
|
+
const result = planSync(config, root, version, assetsDir);
|
|
88
|
+
const counted = new Map();
|
|
89
|
+
for (const entry of result.planned) {
|
|
90
|
+
counted.set(entry.outcome, (counted.get(entry.outcome) ?? 0) + 1);
|
|
91
|
+
}
|
|
92
|
+
const lines = [
|
|
93
|
+
`пакет v${version}, ресурсов ${collectAssets(config, assetsDir).length}, из них пропущено ${config.skip.length}`,
|
|
94
|
+
`значений в конфиге: ${Object.keys(config.vars).length}`,
|
|
95
|
+
...[...counted].map(([outcome, count]) => `${WORD[outcome]}: ${count}`),
|
|
96
|
+
...describe(result),
|
|
97
|
+
];
|
|
98
|
+
return { code: 0, lines };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Три команды и их вывод. Печать отделена от работы: команда возвращает строки и код возврата,\n * а `process.exit` зовёт только точка входа — иначе ни одну из них нельзя было бы проверить\n * спекой, не перехватывая поток вывода.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { collectAssets } from './assets.js';\nimport { CONFIG_PATH, DEFAULT_LAYOUT, IConfig, OVERRIDES_DIR, readConfig } from './config.js';\nimport { IPlanned, isRefusal, TOutcome } from './plan.js';\nimport { ISyncResult, pendingOf, planSync, runSync } from './sync.js';\n\nexport interface IOutcomeOfCommand {\n readonly code: number;\n readonly lines: readonly string[];\n}\n\n/** Всё, что команда знает о мире: корень проекта, версия пакета и его каталог ресурсов. */\nexport interface IEnvironment {\n readonly root: string;\n readonly version: string;\n readonly assetsDir: string;\n}\n\nconst WORD: Readonly<Record<TOutcome, string>> = {\n create: 'положен',\n update: 'переложен',\n drift: 'правлен руками',\n foreign: 'положен не пакетом',\n ok: 'без изменений',\n};\n\nconst NO_CONFIG: string = `нет \\`${CONFIG_PATH}\\` — начни с \\`agent-kit init\\``;\n\nconst holes: (result: ISyncResult) => string[] = (result: ISyncResult): string[] =>\n [...result.missing].map(\n ([asset, names]: [string, readonly string[]]): string =>\n ` ${asset}: нет значений для ${names.map((name: string): string => `{{${name}}}`).join(', ')}`\n );\n\nconst describe: (result: ISyncResult) => string[] = (result: ISyncResult): string[] => [\n ...holes(result),\n ...pendingOf(result).map((entry: IPlanned): string => ` ${entry.path} — ${WORD[entry.outcome]}`),\n];\n\nexport function init(root: string): IOutcomeOfCommand {\n const path: string = join(root, CONFIG_PATH);\n if (existsSync(path)) {\n return { code: 0, lines: [`${CONFIG_PATH} уже есть — оставлен как есть`] };\n }\n\n const config: object = { vars: {}, layout: DEFAULT_LAYOUT, skip: [] };\n mkdirSync(join(root, OVERRIDES_DIR), { recursive: true });\n mkdirSync(join(root, CONFIG_PATH, '..'), { recursive: true });\n writeFileSync(path, `${JSON.stringify(config, null, 4)}\\n`, 'utf8');\n\n return {\n code: 0,\n lines: [\n `заведён ${CONFIG_PATH}`,\n `заведён ${OVERRIDES_DIR}/ — надстройки проекта кладутся сюда путём ресурса`,\n 'дальше: заполни `vars` и запусти `agent-kit sync`',\n ],\n };\n}\n\nexport function sync(env: IEnvironment, check: boolean): IOutcomeOfCommand {\n const { root, version, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n if (!config) {\n return { code: 1, lines: [NO_CONFIG] };\n }\n\n if (check) {\n const result: ISyncResult = planSync(config, root, version, assetsDir);\n const pending: readonly IPlanned[] = pendingOf(result);\n if (!result.missing.size && !pending.length) {\n return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`] };\n }\n\n return { code: 1, lines: [`sync --check: расхождений ${result.missing.size + pending.length}`, ...describe(result)] };\n }\n\n const result: ISyncResult = runSync(config, root, version, assetsDir);\n // Перечисляются только дырки: раскладка не начата, и назвать файл «положенным» значило бы\n // сказать неправду — на диске его нет.\n if (result.missing.size) {\n return { code: 1, lines: ['раскладка не начата: нечего подставить в дырки', ...holes(result)] };\n }\n\n const refused: readonly IPlanned[] = result.planned.filter((entry: IPlanned): boolean => isRefusal(entry.outcome));\n if (refused.length) {\n return {\n code: 1,\n lines: [\n 'раскладка не начата: эти файлы пакет переписывать не станет',\n ...refused.map((entry: IPlanned): string => ` ${entry.path} — ${WORD[entry.outcome]}`),\n 'правку надо либо перенести в надстройку, либо снять — и повторить',\n ],\n };\n }\n\n return {\n code: 0,\n lines: result.written.length\n ? [`разложено файлов: ${result.written.length}`, ...result.written.map((path: string): string => ` ${path}`)]\n : ['всё уже разложено'],\n };\n}\n\nexport function doctor(env: IEnvironment): IOutcomeOfCommand {\n const { root, version, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n if (!config) {\n return { code: 1, lines: [NO_CONFIG] };\n }\n\n const result: ISyncResult = planSync(config, root, version, assetsDir);\n const counted: Map<TOutcome, number> = new Map();\n for (const entry of result.planned) {\n counted.set(entry.outcome, (counted.get(entry.outcome) ?? 0) + 1);\n }\n\n const lines: string[] = [\n `пакет v${version}, ресурсов ${collectAssets(config, assetsDir).length}, из них пропущено ${config.skip.length}`,\n `значений в конфиге: ${Object.keys(config.vars).length}`,\n ...[...counted].map(([outcome, count]: [TOutcome, number]): string => `${WORD[outcome]}: ${count}`),\n ...describe(result),\n ];\n\n return { code: 0, lines };\n}\n"]}
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */
|
|
2
|
+
export type TKind = 'laws' | 'hooks' | 'checks' | 'agents' | 'templates';
|
|
3
|
+
export declare const KINDS: readonly TKind[];
|
|
4
|
+
export interface IConfig {
|
|
5
|
+
/** Значения дырок `{{имя}}`. */
|
|
6
|
+
readonly vars: Readonly<Record<string, string>>;
|
|
7
|
+
/** Куда класть каждый род ресурса, путями от корня проекта. */
|
|
8
|
+
readonly layout: Readonly<Record<TKind, string>>;
|
|
9
|
+
/** Ресурсы, от которых проект отказался, идентификаторами: `laws/money.md`. */
|
|
10
|
+
readonly skip: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export declare const CONFIG_PATH: string;
|
|
13
|
+
export declare const OVERRIDES_DIR: string;
|
|
14
|
+
/**
|
|
15
|
+
* Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:
|
|
16
|
+
* агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет
|
|
17
|
+
* же и везёт.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_LAYOUT: Readonly<Record<TKind, string>>;
|
|
20
|
+
export declare class ConfigError extends Error {
|
|
21
|
+
}
|
|
22
|
+
export declare function parseConfig(text: string): IConfig;
|
|
23
|
+
/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */
|
|
24
|
+
export declare function readConfig(root: string): IConfig | null;
|
|
25
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"AAUA,2EAA2E;AAC3E,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEzE,eAAO,MAAM,KAAK,EAAE,SAAS,KAAK,EAAuD,CAAC;AAE1F,MAAM,WAAW,OAAO;IACpB,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,eAAO,MAAM,WAAW,EAAE,MAA8B,CAAC;AACzD,eAAO,MAAM,aAAa,EAAE,MAAmC,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAM1D,CAAC;AAEF,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAuBzC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA+BjD;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAUvD"}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Конфиг проекта — `.claude/rt-kit.json`.
|
|
3
|
+
*
|
|
4
|
+
* Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от
|
|
5
|
+
* чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана
|
|
6
|
+
* повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
export const KINDS = ['laws', 'hooks', 'checks', 'agents', 'templates'];
|
|
11
|
+
export const CONFIG_PATH = '.claude/rt-kit.json';
|
|
12
|
+
export const OVERRIDES_DIR = '.claude/rt-kit/overrides';
|
|
13
|
+
/**
|
|
14
|
+
* Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:
|
|
15
|
+
* агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет
|
|
16
|
+
* же и везёт.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_LAYOUT = {
|
|
19
|
+
laws: 'docs/constitution',
|
|
20
|
+
hooks: '.claude/hooks',
|
|
21
|
+
checks: 'tools',
|
|
22
|
+
agents: '.claude/agents',
|
|
23
|
+
templates: '.claude/rt-kit/templates',
|
|
24
|
+
};
|
|
25
|
+
export class ConfigError extends Error {
|
|
26
|
+
}
|
|
27
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
function stringMap(value, where) {
|
|
29
|
+
if (value === undefined) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
if (!isRecord(value)) {
|
|
33
|
+
throw new ConfigError(`${where}: ожидается объект «имя — значение»`);
|
|
34
|
+
}
|
|
35
|
+
const result = {};
|
|
36
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
37
|
+
if (typeof entry !== 'string') {
|
|
38
|
+
throw new ConfigError(`${where}.${key}: ожидается строка`);
|
|
39
|
+
}
|
|
40
|
+
result[key] = entry;
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
export function parseConfig(text) {
|
|
45
|
+
let raw;
|
|
46
|
+
try {
|
|
47
|
+
raw = JSON.parse(text);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
if (!isRecord(raw)) {
|
|
53
|
+
throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);
|
|
54
|
+
}
|
|
55
|
+
const layout = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };
|
|
56
|
+
for (const kind of Object.keys(layout)) {
|
|
57
|
+
if (!KINDS.includes(kind)) {
|
|
58
|
+
throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const skip = raw['skip'] ?? [];
|
|
62
|
+
if (!Array.isArray(skip) || skip.some((entry) => typeof entry !== 'string')) {
|
|
63
|
+
throw new ConfigError('skip: ожидается список идентификаторов ресурсов');
|
|
64
|
+
}
|
|
65
|
+
// Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком
|
|
66
|
+
// значило бы держать одно и то же в двух местах: расходятся такие пары молча.
|
|
67
|
+
const derived = {};
|
|
68
|
+
for (const kind of KINDS) {
|
|
69
|
+
derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];
|
|
70
|
+
}
|
|
71
|
+
return { vars: { ...derived, ...stringMap(raw['vars'], 'vars') }, layout: layout, skip: skip };
|
|
72
|
+
}
|
|
73
|
+
/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */
|
|
74
|
+
export function readConfig(root) {
|
|
75
|
+
try {
|
|
76
|
+
return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof ConfigError) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=config.js.map
|