@rt-tools/agent-kit 0.1.0 → 0.3.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 +88 -9
- package/assets/hooks/browser-device-id.sh +20 -0
- package/assets/hooks/browser-guard-device-id.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +17 -0
- package/assets/hooks/browser-guard-no-other-drivers.sh +78 -0
- package/assets/hooks/browser-guard-require-select.sh +53 -0
- package/assets/hooks/commit-msg.sh +26 -0
- package/assets/hooks/constitution-index.sh +42 -0
- package/assets/hooks/dev-server-guard.sh +113 -0
- package/assets/hooks/docs-guard.sh +96 -0
- package/assets/hooks/git-guard-delivery.sh +110 -0
- package/assets/hooks/git-guard-main.sh +72 -0
- package/assets/hooks/git-guard-push-tests.sh +73 -0
- package/assets/hooks/lint-after-edit.sh +94 -0
- package/assets/hooks/qa-dataid-guard.sh +81 -0
- package/assets/hooks/reuse-first-guard.sh +83 -0
- package/assets/hooks/skill-gate-rearm.sh +22 -0
- package/assets/hooks/skill-gate.sh +68 -0
- package/assets/hooks/skill-loaded.sh +20 -0
- package/assets/hooks/sql-guard.sh +129 -0
- package/assets/laws/access.md +3 -12
- package/assets/laws/admin-lists.md +9 -21
- package/assets/laws/admin-navigation.md +3 -15
- package/assets/laws/code-structure.md +5 -16
- package/assets/laws/delivery.md +2 -16
- package/assets/laws/entity-editing.md +7 -20
- package/assets/laws/entity-models.md +10 -17
- package/assets/laws/frontend-application.md +3 -14
- package/assets/laws/lib-imports.md +0 -13
- package/assets/laws/locales.md +2 -11
- package/assets/laws/project-documentation.md +7 -20
- package/assets/laws/reuse-first.md +0 -9
- package/assets/laws/search-visibility.md +0 -13
- package/assets/laws/shared-code.md +0 -13
- package/assets/laws/verifiability.md +0 -13
- package/assets/patterns/angular-patterns-state.md +94 -0
- package/assets/patterns/api-layer-pair.md +78 -0
- package/assets/patterns/browser-verification-measure.md +83 -0
- package/assets/patterns/browser-verification-stand.md +79 -0
- package/assets/patterns/component-structure-new.md +98 -0
- package/assets/patterns/doc-style-sweep.md +100 -0
- package/assets/patterns/doc-style-write.md +106 -0
- package/assets/patterns/git-workflow-commit.md +175 -0
- package/assets/patterns/git-workflow-merge.md +82 -0
- package/assets/patterns/git-workflow-migration.md +58 -0
- package/assets/patterns/git-workflow-restart.md +49 -0
- package/assets/patterns/lib-layers-move.md +77 -0
- package/assets/patterns/lib-layers-new.md +70 -0
- package/assets/patterns/permissions-procedure.md +69 -0
- package/assets/patterns/platform-access-di.md +70 -0
- package/assets/patterns/reuse-first-extend.md +73 -0
- package/assets/patterns/seo-page.md +92 -0
- package/assets/patterns/seo-verify.md +64 -0
- package/assets/patterns/shared-code-new.md +80 -0
- package/assets/patterns/spec-driven-domain.md +100 -0
- package/assets/patterns/spec-driven-rule.md +112 -0
- package/assets/patterns/styling-bem-component.md +77 -0
- package/assets/patterns/styling-bem-layout.md +67 -0
- package/assets/patterns/testing-e2e.md +90 -0
- package/assets/patterns/testing-unit.md +93 -0
- package/assets/patterns/translations-key.md +51 -0
- package/assets/patterns/ts-procedure.md +66 -0
- package/assets/rules/angular-patterns.md +52 -0
- package/assets/rules/api-layer.md +53 -0
- package/assets/rules/browser-verification.md +69 -0
- package/assets/rules/component-structure.md +48 -0
- package/assets/rules/doc-style.md +61 -0
- package/assets/rules/git-workflow.md +106 -0
- package/assets/rules/lib-layers.md +54 -0
- package/assets/rules/permissions.md +52 -0
- package/assets/rules/platform-access.md +49 -0
- package/assets/rules/reuse-first.md +69 -0
- package/assets/rules/seo.md +50 -0
- package/assets/rules/shared-code.md +45 -0
- package/assets/rules/spec-driven.md +89 -0
- package/assets/rules/styling-bem.md +59 -0
- package/assets/rules/testing.md +69 -0
- package/assets/rules/translations.md +52 -0
- package/assets/rules/typescript-conventions.md +46 -0
- package/assets/templates/gate-map.sh +37 -0
- package/assets/templates/implementation.md +38 -0
- package/assets/templates/pattern.md +4 -0
- package/assets/templates/project.sh +41 -0
- package/assets/templates/rule.md +12 -23
- package/bin/agent-kit.d.ts +1 -1
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +63 -7
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +9 -0
- package/bin/prompt.d.ts.map +1 -0
- package/bin/prompt.js +57 -0
- package/bin/prompt.js.map +1 -0
- package/index.d.ts +2 -0
- package/index.d.ts.map +1 -1
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +10 -2
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +24 -28
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +44 -0
- package/lib/catalog.d.ts.map +1 -0
- package/lib/catalog.js +86 -0
- package/lib/catalog.js.map +1 -0
- package/lib/commands.d.ts +13 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +106 -11
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +53 -0
- package/lib/companion.d.ts.map +1 -0
- package/lib/companion.js +33 -0
- package/lib/companion.js.map +1 -0
- package/lib/config.d.ts +29 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +43 -6
- package/lib/config.js.map +1 -1
- package/lib/picker.d.ts +47 -0
- package/lib/picker.d.ts.map +1 -0
- package/lib/picker.js +112 -0
- package/lib/picker.js.map +1 -0
- package/lib/stamp.d.ts +2 -5
- package/lib/stamp.d.ts.map +1 -1
- package/lib/stamp.js +25 -10
- package/lib/stamp.js.map +1 -1
- package/lib/sync.d.ts +3 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +20 -1
- package/lib/sync.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.3.0.tgz +0 -0
- package/rt-tools-agent-kit-0.1.0.tgz +0 -0
package/lib/commands.js
CHANGED
|
@@ -1,33 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Команды и их вывод. Печать отделена от работы: команда возвращает строки и код возврата,
|
|
3
3
|
* а `process.exit` зовёт только точка входа — иначе ни одну из них нельзя было бы проверить
|
|
4
4
|
* спекой, не перехватывая поток вывода.
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { collectAssets } from './assets.js';
|
|
9
|
-
import {
|
|
9
|
+
import { isChosen, readCatalog } from './catalog.js';
|
|
10
|
+
import { isUnfilled } from './companion.js';
|
|
11
|
+
import { CONFIG_PATH, DEFAULT_LAYOUT, KINDS, OVERRIDES_DIR, readConfig } from './config.js';
|
|
10
12
|
import { isRefusal } from './plan.js';
|
|
11
13
|
import { pendingOf, planSync, runSync } from './sync.js';
|
|
12
|
-
|
|
14
|
+
/** Что с файлом сделала раскладка. Прошедшее время здесь правда: `sync` уже записал. */
|
|
15
|
+
const DONE_WORD = {
|
|
13
16
|
create: 'положен',
|
|
14
17
|
update: 'переложен',
|
|
15
18
|
drift: 'правлен руками',
|
|
16
19
|
foreign: 'положен не пакетом',
|
|
17
20
|
ok: 'без изменений',
|
|
18
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* Что с файлом **сейчас**. Отдельно от прошедшего времени потому, что `doctor` и `list` ничего
|
|
24
|
+
* не пишут: сказать «положен» о файле, которого в дереве нет, значит соврать читателю ровно там,
|
|
25
|
+
* где он пришёл узнать состояние.
|
|
26
|
+
*/
|
|
27
|
+
const STATE_WORD = {
|
|
28
|
+
create: 'нет в дереве',
|
|
29
|
+
update: 'отстал от пакета',
|
|
30
|
+
drift: 'правлен руками',
|
|
31
|
+
foreign: 'положен не пакетом',
|
|
32
|
+
ok: 'на месте',
|
|
33
|
+
};
|
|
34
|
+
const NOT_CHOSEN = 'не выбран';
|
|
35
|
+
const SKIPPED = 'пропущен';
|
|
36
|
+
const KIND_TITLE = {
|
|
37
|
+
laws: 'ЗАКОНЫ',
|
|
38
|
+
rules: 'ПРАВИЛА',
|
|
39
|
+
patterns: 'ПАТТЕРНЫ',
|
|
40
|
+
hooks: 'ХУКИ',
|
|
41
|
+
checks: 'ПРОВЕРКИ',
|
|
42
|
+
agents: 'АГЕНТЫ',
|
|
43
|
+
commands: 'КОМАНДЫ',
|
|
44
|
+
workflows: 'ВОРКФЛОУ',
|
|
45
|
+
templates: 'ШАБЛОНЫ',
|
|
46
|
+
};
|
|
47
|
+
/** Состояние компаньона словами. У заполненного слова нет: о нём говорить нечего. */
|
|
48
|
+
const COMPANION_WORD = {
|
|
49
|
+
missing: 'пропал',
|
|
50
|
+
draft: 'остался черновиком',
|
|
51
|
+
filled: 'заполнен',
|
|
52
|
+
};
|
|
19
53
|
const NO_CONFIG = `нет \`${CONFIG_PATH}\` — начни с \`agent-kit init\``;
|
|
20
54
|
const holes = (result) => [...result.missing].map(([asset, names]) => ` ${asset}: нет значений для ${names.map((name) => `{{${name}}}`).join(', ')}`);
|
|
55
|
+
const unfilled = (result) => result.companions.filter(isUnfilled);
|
|
21
56
|
const describe = (result) => [
|
|
22
57
|
...holes(result),
|
|
23
|
-
...pendingOf(result).map((entry) => ` ${entry.path} — ${
|
|
58
|
+
...pendingOf(result).map((entry) => ` ${entry.path} — ${STATE_WORD[entry.outcome]}`),
|
|
59
|
+
...unfilled(result).map((entry) => ` ${entry.path} — ${COMPANION_WORD[entry.state]}`),
|
|
24
60
|
];
|
|
25
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Что проект выбрал при заведении конфига. Пустой список — весь набор; выбор приходит сюда
|
|
63
|
+
* готовым, потому что взять его негде, кроме края: у строки запуска флаг, у терминала вопрос,
|
|
64
|
+
* а у неинтерактивного прогона нет ни того ни другого — и решать это команде не по чину.
|
|
65
|
+
*/
|
|
66
|
+
export function init(root, only = []) {
|
|
26
67
|
const path = join(root, CONFIG_PATH);
|
|
27
68
|
if (existsSync(path)) {
|
|
28
69
|
return { code: 0, lines: [`${CONFIG_PATH} уже есть — оставлен как есть`] };
|
|
29
70
|
}
|
|
30
|
-
const config = { vars: {}, layout: DEFAULT_LAYOUT, skip: [] };
|
|
71
|
+
const config = { vars: {}, layout: DEFAULT_LAYOUT, only, skip: [] };
|
|
31
72
|
mkdirSync(join(root, OVERRIDES_DIR), { recursive: true });
|
|
32
73
|
mkdirSync(join(root, CONFIG_PATH, '..'), { recursive: true });
|
|
33
74
|
writeFileSync(path, `${JSON.stringify(config, null, 4)}\n`, 'utf8');
|
|
@@ -35,6 +76,7 @@ export function init(root) {
|
|
|
35
76
|
code: 0,
|
|
36
77
|
lines: [
|
|
37
78
|
`заведён ${CONFIG_PATH}`,
|
|
79
|
+
only.length ? `выбрано ресурсов: ${only.length}` : 'выбрано всё, что везёт пакет',
|
|
38
80
|
`заведён ${OVERRIDES_DIR}/ — надстройки проекта кладутся сюда путём ресурса`,
|
|
39
81
|
'дальше: заполни `vars` и запусти `agent-kit sync`',
|
|
40
82
|
],
|
|
@@ -49,10 +91,16 @@ export function sync(env, check) {
|
|
|
49
91
|
if (check) {
|
|
50
92
|
const result = planSync(config, root, version, assetsDir);
|
|
51
93
|
const pending = pendingOf(result);
|
|
52
|
-
|
|
94
|
+
// Незаполненный компаньон — такое же расхождение, как отставший файл: правило разложено,
|
|
95
|
+
// а имён этого дерева при нём нет, и агент читает указание, которому некуда примениться.
|
|
96
|
+
const empty = unfilled(result);
|
|
97
|
+
if (!result.missing.size && !pending.length && !empty.length) {
|
|
53
98
|
return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`] };
|
|
54
99
|
}
|
|
55
|
-
return {
|
|
100
|
+
return {
|
|
101
|
+
code: 1,
|
|
102
|
+
lines: [`sync --check: расхождений ${result.missing.size + pending.length + empty.length}`, ...describe(result)],
|
|
103
|
+
};
|
|
56
104
|
}
|
|
57
105
|
const result = runSync(config, root, version, assetsDir);
|
|
58
106
|
// Перечисляются только дырки: раскладка не начата, и назвать файл «положенным» значило бы
|
|
@@ -66,7 +114,7 @@ export function sync(env, check) {
|
|
|
66
114
|
code: 1,
|
|
67
115
|
lines: [
|
|
68
116
|
'раскладка не начата: эти файлы пакет переписывать не станет',
|
|
69
|
-
...refused.map((entry) => ` ${entry.path} — ${
|
|
117
|
+
...refused.map((entry) => ` ${entry.path} — ${DONE_WORD[entry.outcome]}`),
|
|
70
118
|
'правку надо либо перенести в надстройку, либо снять — и повторить',
|
|
71
119
|
],
|
|
72
120
|
};
|
|
@@ -89,12 +137,59 @@ export function doctor(env) {
|
|
|
89
137
|
for (const entry of result.planned) {
|
|
90
138
|
counted.set(entry.outcome, (counted.get(entry.outcome) ?? 0) + 1);
|
|
91
139
|
}
|
|
140
|
+
const catalog = readCatalog(assetsDir);
|
|
141
|
+
const taken = collectAssets(config, assetsDir).length;
|
|
92
142
|
const lines = [
|
|
93
|
-
`пакет v${version}, ресурсов ${
|
|
143
|
+
`пакет v${version}, везёт ресурсов ${catalog.length}, взято ${taken}`,
|
|
144
|
+
`не выбрано: ${catalog.length - taken - config.skip.length}, пропущено: ${config.skip.length}`,
|
|
94
145
|
`значений в конфиге: ${Object.keys(config.vars).length}`,
|
|
95
|
-
...[...counted].map(([outcome, count]) => `${
|
|
146
|
+
...[...counted].map(([outcome, count]) => `${STATE_WORD[outcome]}: ${count}`),
|
|
96
147
|
...describe(result),
|
|
97
148
|
];
|
|
98
149
|
return { code: 0, lines };
|
|
99
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Что пакет везёт и что из этого взято здесь.
|
|
153
|
+
*
|
|
154
|
+
* Работает и без конфига: посмотреть, из чего выбирать, нужно раньше, чем выбор сделан, — иначе
|
|
155
|
+
* имена законов взять неоткуда, кроме исходников пакета.
|
|
156
|
+
*/
|
|
157
|
+
export function list(env) {
|
|
158
|
+
const { root, version, assetsDir } = env;
|
|
159
|
+
const config = readConfig(root);
|
|
160
|
+
const catalog = readCatalog(assetsDir);
|
|
161
|
+
const planned = new Map();
|
|
162
|
+
if (config) {
|
|
163
|
+
for (const entry of planSync(config, root, version, assetsDir).planned) {
|
|
164
|
+
planned.set(entry.asset, entry.outcome);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const stateOf = (entry) => {
|
|
168
|
+
if (!config) {
|
|
169
|
+
return 'везёт пакет';
|
|
170
|
+
}
|
|
171
|
+
if (config.skip.includes(entry.id)) {
|
|
172
|
+
return SKIPPED;
|
|
173
|
+
}
|
|
174
|
+
if (!isChosen(entry, config.only, config.skip)) {
|
|
175
|
+
return NOT_CHOSEN;
|
|
176
|
+
}
|
|
177
|
+
const outcome = planned.get(entry.id);
|
|
178
|
+
// Ресурса нет среди запланированных ровно в одном случае: в нём осталась дырка без
|
|
179
|
+
// значения, и до решения о судьбе файла раскладка не дошла.
|
|
180
|
+
return outcome ? STATE_WORD[outcome] : 'дырка без значения';
|
|
181
|
+
};
|
|
182
|
+
const name = catalog.reduce((found, entry) => Math.max(found, entry.name.length), 0);
|
|
183
|
+
const state = catalog.reduce((found, entry) => Math.max(found, stateOf(entry).length), 0);
|
|
184
|
+
const lines = [config ? `пакет v${version}` : `пакет v${version}, ${CONFIG_PATH} ещё не заведён`];
|
|
185
|
+
for (const kind of KINDS) {
|
|
186
|
+
const entries = catalog.filter((entry) => entry.kind === kind);
|
|
187
|
+
if (!entries.length) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
lines.push('', KIND_TITLE[kind]);
|
|
191
|
+
lines.push(...entries.map((entry) => ` ${entry.name.padEnd(name)} ${stateOf(entry).padEnd(state)} ${entry.title}`));
|
|
192
|
+
}
|
|
193
|
+
return { code: 0, lines };
|
|
194
|
+
}
|
|
100
195
|
//# sourceMappingURL=commands.js.map
|
package/lib/commands.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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 { IEntryOfCatalog, isChosen, readCatalog } from './catalog.js';\nimport { ICompanion, isUnfilled, TCompanionState } from './companion.js';\nimport { CONFIG_PATH, DEFAULT_LAYOUT, IConfig, KINDS, OVERRIDES_DIR, readConfig, TKind } 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\n/** Что с файлом сделала раскладка. Прошедшее время здесь правда: `sync` уже записал. */\nconst DONE_WORD: Readonly<Record<TOutcome, string>> = {\n create: 'положен',\n update: 'переложен',\n drift: 'правлен руками',\n foreign: 'положен не пакетом',\n ok: 'без изменений',\n};\n\n/**\n * Что с файлом **сейчас**. Отдельно от прошедшего времени потому, что `doctor` и `list` ничего\n * не пишут: сказать «положен» о файле, которого в дереве нет, значит соврать читателю ровно там,\n * где он пришёл узнать состояние.\n */\nconst STATE_WORD: Readonly<Record<TOutcome, string>> = {\n create: 'нет в дереве',\n update: 'отстал от пакета',\n drift: 'правлен руками',\n foreign: 'положен не пакетом',\n ok: 'на месте',\n};\n\nconst NOT_CHOSEN: string = 'не выбран';\nconst SKIPPED: string = 'пропущен';\n\nconst KIND_TITLE: Readonly<Record<TKind, string>> = {\n laws: 'ЗАКОНЫ',\n rules: 'ПРАВИЛА',\n patterns: 'ПАТТЕРНЫ',\n hooks: 'ХУКИ',\n checks: 'ПРОВЕРКИ',\n agents: 'АГЕНТЫ',\n commands: 'КОМАНДЫ',\n workflows: 'ВОРКФЛОУ',\n templates: 'ШАБЛОНЫ',\n};\n\n/** Состояние компаньона словами. У заполненного слова нет: о нём говорить нечего. */\nconst COMPANION_WORD: Readonly<Record<TCompanionState, string>> = {\n missing: 'пропал',\n draft: 'остался черновиком',\n filled: 'заполнен',\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 unfilled: (result: ISyncResult) => readonly ICompanion[] = (result: ISyncResult): readonly ICompanion[] =>\n result.companions.filter(isUnfilled);\n\nconst describe: (result: ISyncResult) => string[] = (result: ISyncResult): string[] => [\n ...holes(result),\n ...pendingOf(result).map((entry: IPlanned): string => ` ${entry.path} — ${STATE_WORD[entry.outcome]}`),\n ...unfilled(result).map((entry: ICompanion): string => ` ${entry.path} — ${COMPANION_WORD[entry.state]}`),\n];\n\n/**\n * Что проект выбрал при заведении конфига. Пустой список — весь набор; выбор приходит сюда\n * готовым, потому что взять его негде, кроме края: у строки запуска флаг, у терминала вопрос,\n * а у неинтерактивного прогона нет ни того ни другого — и решать это команде не по чину.\n */\nexport function init(root: string, only: readonly 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, only, 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 only.length ? `выбрано ресурсов: ${only.length}` : 'выбрано всё, что везёт пакет',\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 // Незаполненный компаньон — такое же расхождение, как отставший файл: правило разложено,\n // а имён этого дерева при нём нет, и агент читает указание, которому некуда примениться.\n const empty: readonly ICompanion[] = unfilled(result);\n if (!result.missing.size && !pending.length && !empty.length) {\n return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`] };\n }\n\n return {\n code: 1,\n lines: [`sync --check: расхождений ${result.missing.size + pending.length + empty.length}`, ...describe(result)],\n };\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} — ${DONE_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 catalog: readonly IEntryOfCatalog[] = readCatalog(assetsDir);\n const taken: number = collectAssets(config, assetsDir).length;\n\n const lines: string[] = [\n `пакет v${version}, везёт ресурсов ${catalog.length}, взято ${taken}`,\n `не выбрано: ${catalog.length - taken - config.skip.length}, пропущено: ${config.skip.length}`,\n `значений в конфиге: ${Object.keys(config.vars).length}`,\n ...[...counted].map(([outcome, count]: [TOutcome, number]): string => `${STATE_WORD[outcome]}: ${count}`),\n ...describe(result),\n ];\n\n return { code: 0, lines };\n}\n\n/**\n * Что пакет везёт и что из этого взято здесь.\n *\n * Работает и без конфига: посмотреть, из чего выбирать, нужно раньше, чем выбор сделан, — иначе\n * имена законов взять неоткуда, кроме исходников пакета.\n */\nexport function list(env: IEnvironment): IOutcomeOfCommand {\n const { root, version, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n const catalog: readonly IEntryOfCatalog[] = readCatalog(assetsDir);\n const planned: Map<string, TOutcome> = new Map();\n\n if (config) {\n for (const entry of planSync(config, root, version, assetsDir).planned) {\n planned.set(entry.asset, entry.outcome);\n }\n }\n\n const stateOf: (entry: IEntryOfCatalog) => string = (entry: IEntryOfCatalog): string => {\n if (!config) {\n return 'везёт пакет';\n }\n if (config.skip.includes(entry.id)) {\n return SKIPPED;\n }\n if (!isChosen(entry, config.only, config.skip)) {\n return NOT_CHOSEN;\n }\n const outcome: TOutcome | undefined = planned.get(entry.id);\n\n // Ресурса нет среди запланированных ровно в одном случае: в нём осталась дырка без\n // значения, и до решения о судьбе файла раскладка не дошла.\n return outcome ? STATE_WORD[outcome] : 'дырка без значения';\n };\n\n const name: number = catalog.reduce((found: number, entry: IEntryOfCatalog): number => Math.max(found, entry.name.length), 0);\n const state: number = catalog.reduce((found: number, entry: IEntryOfCatalog): number => Math.max(found, stateOf(entry).length), 0);\n const lines: string[] = [config ? `пакет v${version}` : `пакет v${version}, ${CONFIG_PATH} ещё не заведён`];\n\n for (const kind of KINDS) {\n const entries: readonly IEntryOfCatalog[] = catalog.filter((entry: IEntryOfCatalog): boolean => entry.kind === kind);\n if (!entries.length) {\n continue;\n }\n lines.push('', KIND_TITLE[kind]);\n lines.push(\n ...entries.map(\n (entry: IEntryOfCatalog): string => ` ${entry.name.padEnd(name)} ${stateOf(entry).padEnd(state)} ${entry.title}`\n )\n );\n }\n\n return { code: 0, lines };\n}\n"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.
|
|
3
|
+
*
|
|
4
|
+
* Правило пакета переносимо ровно потому, что не знает ни путей, ни имён: оно говорит, какой
|
|
5
|
+
* приём здесь действует и что сломается без него. Но правило без имён дерева — это закон, и
|
|
6
|
+
* агент по нему работать не может: ему нужно знать, как называется здесь то, о чём идёт речь,
|
|
7
|
+
* и где оно лежит.
|
|
8
|
+
*
|
|
9
|
+
* Поэтому имена живут рядом с правилом, в отдельном файле, и пишет его проект. Пакет кладёт
|
|
10
|
+
* черновик один раз и больше к нему не возвращается: сверять его нечем — своего текста у пакета
|
|
11
|
+
* там нет, а перекладывать значило бы стирать написанное проектом.
|
|
12
|
+
*
|
|
13
|
+
* Отсюда и три состояния вместо двух. Черновик, который никто не заполнил, — не то же самое,
|
|
14
|
+
* что заполненный компаньон, и не то же самое, что пропавший: он выглядит рабочим файлом, но
|
|
15
|
+
* правило при нём остаётся указанием без адресата.
|
|
16
|
+
*/
|
|
17
|
+
import { IAsset } from './assets.js';
|
|
18
|
+
/**
|
|
19
|
+
* Метка незаполненного места. Стоит в каждой строке черновика, которую должен заменить проект,
|
|
20
|
+
* и снимается вместе с ней: пока метка на месте, компаньон не заполнен.
|
|
21
|
+
*/
|
|
22
|
+
export declare const COMPANION_MARK: string;
|
|
23
|
+
export type TCompanionState =
|
|
24
|
+
/** Файла нет: правило разложено, а имён дерева при нём не оказалось. */
|
|
25
|
+
'missing'
|
|
26
|
+
/** Черновик лежит, но остался как положен. */
|
|
27
|
+
| 'draft'
|
|
28
|
+
/** Проект его заполнил. */
|
|
29
|
+
| 'filled';
|
|
30
|
+
export interface ICompanion {
|
|
31
|
+
/** Правило, при котором стоит компаньон. */
|
|
32
|
+
readonly rule: string;
|
|
33
|
+
readonly path: string;
|
|
34
|
+
readonly state: TCompanionState;
|
|
35
|
+
/** Что записать; `null` — писать нечего, файл уже лежит. */
|
|
36
|
+
readonly content: string | null;
|
|
37
|
+
}
|
|
38
|
+
/** Путь компаньона: рядом с самим правилом, в каталоге его имени. */
|
|
39
|
+
export declare const pathOf: (asset: IAsset) => string;
|
|
40
|
+
/**
|
|
41
|
+
* Место имени правила в шаблоне. Угловые скобки, а не `{{дырка}}`: дырка без значения отказывает
|
|
42
|
+
* раскладке, а шаблон компаньона пакет кладёт проекту и сам — на нём раскладка и споткнулась бы.
|
|
43
|
+
*/
|
|
44
|
+
export declare const RULE_SLOT: string;
|
|
45
|
+
/**
|
|
46
|
+
* Черновик по шаблону пакета. Имя правила подставляется, чтобы заполняющий не гадал, к чему
|
|
47
|
+
* файл относится, — открывают его обычно из гейта, а не из правила.
|
|
48
|
+
*/
|
|
49
|
+
export declare const draftOf: (template: string, rule: string) => string;
|
|
50
|
+
export declare function planCompanion(asset: IAsset, existing: string | null, template: string): ICompanion;
|
|
51
|
+
/** Что `sync --check` считает недоделанным: правило при пустом компаньоне не действует. */
|
|
52
|
+
export declare const isUnfilled: (companion: ICompanion) => boolean;
|
|
53
|
+
//# sourceMappingURL=companion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"companion.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/companion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,MAAoC,CAAC;AAElE,MAAM,MAAM,eAAe;AACvB,wEAAwE;AACtE,SAAS;AACX,8CAA8C;GAC5C,OAAO;AACT,2BAA2B;GACzB,QAAQ,CAAC;AAEf,MAAM,WAAW,UAAU;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,qEAAqE;AACrE,eAAO,MAAM,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAkF,CAAC;AAE3H;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,MAAwB,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAClB,CAAC;AAEzC,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAYlG;AAED,2FAA2F;AAC3F,eAAO,MAAM,UAAU,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAA0E,CAAC"}
|
package/lib/companion.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { COMPANION_FILE } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Метка незаполненного места. Стоит в каждой строке черновика, которую должен заменить проект,
|
|
4
|
+
* и снимается вместе с ней: пока метка на месте, компаньон не заполнен.
|
|
5
|
+
*/
|
|
6
|
+
export const COMPANION_MARK = '<!-- заполняет проект -->';
|
|
7
|
+
/** Путь компаньона: рядом с самим правилом, в каталоге его имени. */
|
|
8
|
+
export const pathOf = (asset) => asset.target.replace(/[^/]+$/, COMPANION_FILE);
|
|
9
|
+
/**
|
|
10
|
+
* Место имени правила в шаблоне. Угловые скобки, а не `{{дырка}}`: дырка без значения отказывает
|
|
11
|
+
* раскладке, а шаблон компаньона пакет кладёт проекту и сам — на нём раскладка и споткнулась бы.
|
|
12
|
+
*/
|
|
13
|
+
export const RULE_SLOT = '<имя-правила>';
|
|
14
|
+
/**
|
|
15
|
+
* Черновик по шаблону пакета. Имя правила подставляется, чтобы заполняющий не гадал, к чему
|
|
16
|
+
* файл относится, — открывают его обычно из гейта, а не из правила.
|
|
17
|
+
*/
|
|
18
|
+
export const draftOf = (template, rule) => template.split(RULE_SLOT).join(rule);
|
|
19
|
+
export function planCompanion(asset, existing, template) {
|
|
20
|
+
const path = pathOf(asset);
|
|
21
|
+
if (existing === null) {
|
|
22
|
+
return { rule: asset.name, path, state: 'missing', content: draftOf(template, asset.name) };
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
rule: asset.name,
|
|
26
|
+
path,
|
|
27
|
+
state: existing.includes(COMPANION_MARK) ? 'draft' : 'filled',
|
|
28
|
+
content: null,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Что `sync --check` считает недоделанным: правило при пустом компаньоне не действует. */
|
|
32
|
+
export const isUnfilled = (companion) => companion.state !== 'filled';
|
|
33
|
+
//# sourceMappingURL=companion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"companion.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/companion.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAW,2BAA2B,CAAC;AAmBlE,qEAAqE;AACrE,MAAM,CAAC,MAAM,MAAM,GAA8B,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AAE3H;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAW,eAAe,CAAC;AAEjD;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAA+C,CAAC,QAAgB,EAAE,IAAY,EAAU,EAAE,CAC1G,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEzC,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,QAAuB,EAAE,QAAgB;IAClF,MAAM,IAAI,GAAW,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IAChG,CAAC;IAED,OAAO;QACH,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI;QACJ,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;QAC7D,OAAO,EAAE,IAAI;KAChB,CAAC;AACN,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,MAAM,UAAU,GAAuC,CAAC,SAAqB,EAAW,EAAE,CAAC,SAAS,CAAC,KAAK,KAAK,QAAQ,CAAC","sourcesContent":["/**\n * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.\n *\n * Правило пакета переносимо ровно потому, что не знает ни путей, ни имён: оно говорит, какой\n * приём здесь действует и что сломается без него. Но правило без имён дерева — это закон, и\n * агент по нему работать не может: ему нужно знать, как называется здесь то, о чём идёт речь,\n * и где оно лежит.\n *\n * Поэтому имена живут рядом с правилом, в отдельном файле, и пишет его проект. Пакет кладёт\n * черновик один раз и больше к нему не возвращается: сверять его нечем — своего текста у пакета\n * там нет, а перекладывать значило бы стирать написанное проектом.\n *\n * Отсюда и три состояния вместо двух. Черновик, который никто не заполнил, — не то же самое,\n * что заполненный компаньон, и не то же самое, что пропавший: он выглядит рабочим файлом, но\n * правило при нём остаётся указанием без адресата.\n */\nimport { IAsset } from './assets.js';\nimport { COMPANION_FILE } from './config.js';\n\n/**\n * Метка незаполненного места. Стоит в каждой строке черновика, которую должен заменить проект,\n * и снимается вместе с ней: пока метка на месте, компаньон не заполнен.\n */\nexport const COMPANION_MARK: string = '<!-- заполняет проект -->';\n\nexport type TCompanionState =\n /** Файла нет: правило разложено, а имён дерева при нём не оказалось. */\n | 'missing'\n /** Черновик лежит, но остался как положен. */\n | 'draft'\n /** Проект его заполнил. */\n | 'filled';\n\nexport interface ICompanion {\n /** Правило, при котором стоит компаньон. */\n readonly rule: string;\n readonly path: string;\n readonly state: TCompanionState;\n /** Что записать; `null` — писать нечего, файл уже лежит. */\n readonly content: string | null;\n}\n\n/** Путь компаньона: рядом с самим правилом, в каталоге его имени. */\nexport const pathOf: (asset: IAsset) => string = (asset: IAsset): string => asset.target.replace(/[^/]+$/, COMPANION_FILE);\n\n/**\n * Место имени правила в шаблоне. Угловые скобки, а не `{{дырка}}`: дырка без значения отказывает\n * раскладке, а шаблон компаньона пакет кладёт проекту и сам — на нём раскладка и споткнулась бы.\n */\nexport const RULE_SLOT: string = '<имя-правила>';\n\n/**\n * Черновик по шаблону пакета. Имя правила подставляется, чтобы заполняющий не гадал, к чему\n * файл относится, — открывают его обычно из гейта, а не из правила.\n */\nexport const draftOf: (template: string, rule: string) => string = (template: string, rule: string): string =>\n template.split(RULE_SLOT).join(rule);\n\nexport function planCompanion(asset: IAsset, existing: string | null, template: string): ICompanion {\n const path: string = pathOf(asset);\n if (existing === null) {\n return { rule: asset.name, path, state: 'missing', content: draftOf(template, asset.name) };\n }\n\n return {\n rule: asset.name,\n path,\n state: existing.includes(COMPANION_MARK) ? 'draft' : 'filled',\n content: null,\n };\n}\n\n/** Что `sync --check` считает недоделанным: правило при пустом компаньоне не действует. */\nexport const isUnfilled: (companion: ICompanion) => boolean = (companion: ICompanion): boolean => companion.state !== 'filled';\n"]}
|
package/lib/config.d.ts
CHANGED
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */
|
|
2
|
-
export type TKind = 'laws' | 'hooks' | 'checks' | 'agents' | 'templates';
|
|
2
|
+
export type TKind = 'laws' | 'rules' | 'patterns' | 'hooks' | 'checks' | 'agents' | 'commands' | 'workflows' | 'templates';
|
|
3
3
|
export declare const KINDS: readonly TKind[];
|
|
4
|
+
/**
|
|
5
|
+
* Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как
|
|
6
|
+
* `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SKILL_KINDS: readonly TKind[];
|
|
9
|
+
/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */
|
|
10
|
+
export declare const SKILL_FILE: string;
|
|
11
|
+
/**
|
|
12
|
+
* Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.
|
|
13
|
+
*
|
|
14
|
+
* Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один
|
|
15
|
+
* раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.
|
|
16
|
+
*/
|
|
17
|
+
export declare const COMPANION_FILE: string;
|
|
4
18
|
export interface IConfig {
|
|
5
19
|
/** Значения дырок `{{имя}}`. */
|
|
6
20
|
readonly vars: Readonly<Record<string, string>>;
|
|
7
21
|
/** Куда класть каждый род ресурса, путями от корня проекта. */
|
|
8
22
|
readonly layout: Readonly<Record<TKind, string>>;
|
|
23
|
+
/**
|
|
24
|
+
* Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.
|
|
25
|
+
* Ограничивает только те роды, которые сам называет; как именно — `isChosen`.
|
|
26
|
+
*/
|
|
27
|
+
readonly only: readonly string[];
|
|
9
28
|
/** Ресурсы, от которых проект отказался, идентификаторами: `laws/money.md`. */
|
|
10
29
|
readonly skip: readonly string[];
|
|
11
30
|
}
|
|
12
31
|
export declare const CONFIG_PATH: string;
|
|
13
32
|
export declare const OVERRIDES_DIR: string;
|
|
33
|
+
/** Имя дырки под путь карты гейта и её умолчание: карту пишет проект, путь знают оба. */
|
|
34
|
+
export declare const GATE_MAP_VAR: string;
|
|
35
|
+
export declare const DEFAULT_GATE_MAP: string;
|
|
36
|
+
/**
|
|
37
|
+
* Профиль проекта: команды, стенды и пары «правка — документ» этого дерева. Его читают
|
|
38
|
+
* сторожевые хуки — механизм у них общий, а всё, что они зовут и называют, своё у каждого.
|
|
39
|
+
*/
|
|
40
|
+
export declare const PROFILE_VAR: string;
|
|
41
|
+
export declare const DEFAULT_PROFILE: string;
|
|
14
42
|
/**
|
|
15
43
|
* Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:
|
|
16
44
|
* агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет
|
package/lib/config.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;AAE3H,eAAO,MAAM,KAAK,EAAE,SAAS,KAAK,EAAqG,CAAC;AAExI;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,KAAK,EAA0B,CAAC;AAEnE,iFAAiF;AACjF,eAAO,MAAM,UAAU,EAAE,MAAmB,CAAC;AAE7C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,MAA4B,CAAC;AAE1D,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;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,+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,yFAAyF;AACzF,eAAO,MAAM,YAAY,EAAE,MAAkB,CAAC;AAC9C,eAAO,MAAM,gBAAgB,EAAE,MAAqC,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,MAAyB,CAAC;AACpD,eAAO,MAAM,eAAe,EAAE,MAAoC,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAU1D,CAAC;AAEF,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAgCzC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkCjD;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAUvD"}
|
package/lib/config.js
CHANGED
|
@@ -7,9 +7,32 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { readFileSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
-
export const KINDS = ['laws', 'hooks', 'checks', 'agents', 'templates'];
|
|
10
|
+
export const KINDS = ['laws', 'rules', 'patterns', 'hooks', 'checks', 'agents', 'commands', 'workflows', 'templates'];
|
|
11
|
+
/**
|
|
12
|
+
* Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как
|
|
13
|
+
* `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.
|
|
14
|
+
*/
|
|
15
|
+
export const SKILL_KINDS = ['rules', 'patterns'];
|
|
16
|
+
/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */
|
|
17
|
+
export const SKILL_FILE = 'SKILL.md';
|
|
18
|
+
/**
|
|
19
|
+
* Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.
|
|
20
|
+
*
|
|
21
|
+
* Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один
|
|
22
|
+
* раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.
|
|
23
|
+
*/
|
|
24
|
+
export const COMPANION_FILE = 'implementation.md';
|
|
11
25
|
export const CONFIG_PATH = '.claude/rt-kit.json';
|
|
12
26
|
export const OVERRIDES_DIR = '.claude/rt-kit/overrides';
|
|
27
|
+
/** Имя дырки под путь карты гейта и её умолчание: карту пишет проект, путь знают оба. */
|
|
28
|
+
export const GATE_MAP_VAR = 'gateMap';
|
|
29
|
+
export const DEFAULT_GATE_MAP = '.claude/rt-kit/gate-map.sh';
|
|
30
|
+
/**
|
|
31
|
+
* Профиль проекта: команды, стенды и пары «правка — документ» этого дерева. Его читают
|
|
32
|
+
* сторожевые хуки — механизм у них общий, а всё, что они зовут и называют, своё у каждого.
|
|
33
|
+
*/
|
|
34
|
+
export const PROFILE_VAR = 'projectProfile';
|
|
35
|
+
export const DEFAULT_PROFILE = '.claude/rt-kit/project.sh';
|
|
13
36
|
/**
|
|
14
37
|
* Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:
|
|
15
38
|
* агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет
|
|
@@ -17,9 +40,13 @@ export const OVERRIDES_DIR = '.claude/rt-kit/overrides';
|
|
|
17
40
|
*/
|
|
18
41
|
export const DEFAULT_LAYOUT = {
|
|
19
42
|
laws: 'docs/constitution',
|
|
43
|
+
rules: '.claude/skills',
|
|
44
|
+
patterns: '.claude/skills',
|
|
20
45
|
hooks: '.claude/hooks',
|
|
21
46
|
checks: 'tools',
|
|
22
47
|
agents: '.claude/agents',
|
|
48
|
+
commands: '.claude/commands',
|
|
49
|
+
workflows: '.claude/workflows',
|
|
23
50
|
templates: '.claude/rt-kit/templates',
|
|
24
51
|
};
|
|
25
52
|
export class ConfigError extends Error {
|
|
@@ -41,6 +68,13 @@ function stringMap(value, where) {
|
|
|
41
68
|
}
|
|
42
69
|
return result;
|
|
43
70
|
}
|
|
71
|
+
function idList(value, where) {
|
|
72
|
+
const list = value ?? [];
|
|
73
|
+
if (!Array.isArray(list) || list.some((entry) => typeof entry !== 'string')) {
|
|
74
|
+
throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);
|
|
75
|
+
}
|
|
76
|
+
return list;
|
|
77
|
+
}
|
|
44
78
|
export function parseConfig(text) {
|
|
45
79
|
let raw;
|
|
46
80
|
try {
|
|
@@ -58,17 +92,20 @@ export function parseConfig(text) {
|
|
|
58
92
|
throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);
|
|
59
93
|
}
|
|
60
94
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
throw new ConfigError('skip: ожидается список идентификаторов ресурсов');
|
|
64
|
-
}
|
|
95
|
+
const only = idList(raw['only'], 'only');
|
|
96
|
+
const skip = idList(raw['skip'], 'skip');
|
|
65
97
|
// Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком
|
|
66
98
|
// значило бы держать одно и то же в двух местах: расходятся такие пары молча.
|
|
67
99
|
const derived = {};
|
|
68
100
|
for (const kind of KINDS) {
|
|
69
101
|
derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];
|
|
70
102
|
}
|
|
71
|
-
|
|
103
|
+
// Карту гейта пишет проект, но путь к ней знают обе стороны: хук её читает, шаблон
|
|
104
|
+
// ложится рядом. Умолчание здесь избавляет проект от обязанности объявлять его самому —
|
|
105
|
+
// а переопределить его он всё равно может, значением в `vars`.
|
|
106
|
+
derived[GATE_MAP_VAR] = DEFAULT_GATE_MAP;
|
|
107
|
+
derived[PROFILE_VAR] = DEFAULT_PROFILE;
|
|
108
|
+
return { vars: { ...derived, ...stringMap(raw['vars'], 'vars') }, layout: layout, only, skip };
|
|
72
109
|
}
|
|
73
110
|
/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */
|
|
74
111
|
export function readConfig(root) {
|
package/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind = 'laws' | 'hooks' | 'checks' | 'agents' | 'templates';\n\nexport const KINDS: readonly TKind[] = ['laws', 'hooks', 'checks', 'agents', 'templates'];\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/money.md`. */\n readonly skip: readonly string[];\n}\n\nexport const CONFIG_PATH: string = '.claude/rt-kit.json';\nexport const OVERRIDES_DIR: string = '.claude/rt-kit/overrides';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n hooks: '.claude/hooks',\n checks: 'tools',\n agents: '.claude/agents',\n templates: '.claude/rt-kit/templates',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const skip: unknown = raw['skip'] ?? [];\n if (!Array.isArray(skip) || skip.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError('skip: ожидается список идентификаторов ресурсов');\n }\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n\n return { vars: { ...derived, ...stringMap(raw['vars'], 'vars') }, layout: layout as Record<TKind, string>, skip: skip as string[] };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind = 'laws' | 'rules' | 'patterns' | 'hooks' | 'checks' | 'agents' | 'commands' | 'workflows' | 'templates';\n\nexport const KINDS: readonly TKind[] = ['laws', 'rules', 'patterns', 'hooks', 'checks', 'agents', 'commands', 'workflows', 'templates'];\n\n/**\n * Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как\n * `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.\n */\nexport const SKILL_KINDS: readonly TKind[] = ['rules', 'patterns'];\n\n/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */\nexport const SKILL_FILE: string = 'SKILL.md';\n\n/**\n * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.\n *\n * Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один\n * раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.\n */\nexport const COMPANION_FILE: string = 'implementation.md';\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /**\n * Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.\n * Ограничивает только те роды, которые сам называет; как именно — `isChosen`.\n */\n readonly only: readonly string[];\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/money.md`. */\n readonly skip: readonly string[];\n}\n\nexport const CONFIG_PATH: string = '.claude/rt-kit.json';\nexport const OVERRIDES_DIR: string = '.claude/rt-kit/overrides';\n\n/** Имя дырки под путь карты гейта и её умолчание: карту пишет проект, путь знают оба. */\nexport const GATE_MAP_VAR: string = 'gateMap';\nexport const DEFAULT_GATE_MAP: string = '.claude/rt-kit/gate-map.sh';\n\n/**\n * Профиль проекта: команды, стенды и пары «правка — документ» этого дерева. Его читают\n * сторожевые хуки — механизм у них общий, а всё, что они зовут и называют, своё у каждого.\n */\nexport const PROFILE_VAR: string = 'projectProfile';\nexport const DEFAULT_PROFILE: string = '.claude/rt-kit/project.sh';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n rules: '.claude/skills',\n patterns: '.claude/skills',\n hooks: '.claude/hooks',\n checks: 'tools',\n agents: '.claude/agents',\n commands: '.claude/commands',\n workflows: '.claude/workflows',\n templates: '.claude/rt-kit/templates',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\nfunction idList(value: unknown, where: string): readonly string[] {\n const list: unknown = value ?? [];\n if (!Array.isArray(list) || list.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);\n }\n\n return list as string[];\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const only: readonly string[] = idList(raw['only'], 'only');\n const skip: readonly string[] = idList(raw['skip'], 'skip');\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n // Карту гейта пишет проект, но путь к ней знают обе стороны: хук её читает, шаблон\n // ложится рядом. Умолчание здесь избавляет проект от обязанности объявлять его самому —\n // а переопределить его он всё равно может, значением в `vars`.\n derived[GATE_MAP_VAR] = DEFAULT_GATE_MAP;\n derived[PROFILE_VAR] = DEFAULT_PROFILE;\n\n return { vars: { ...derived, ...stringMap(raw['vars'], 'vars') }, layout: layout as Record<TKind, string>, only, skip };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
|
package/lib/picker.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Выбиралка галочками — её состояние и разметка, без единого касания терминала.
|
|
3
|
+
*
|
|
4
|
+
* Отделено от ввода-вывода по той же причине, что и команды: нажатие клавиши — это переход
|
|
5
|
+
* состояния, и проверять его надо спекой, а не глазами на живом терминале. Сырой режим,
|
|
6
|
+
* перерисовка и восстановление курсора живут на краю, в `bin/prompt.ts`, и не решают ничего.
|
|
7
|
+
*/
|
|
8
|
+
export interface IChoice {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
/** Имя, которым ресурс называют в строке запуска: `access`. */
|
|
11
|
+
readonly name: string;
|
|
12
|
+
/** Заголовок ресурса — им человек и выбирает, имена ему ни о чём не говорят. */
|
|
13
|
+
readonly title: string;
|
|
14
|
+
}
|
|
15
|
+
export interface IPickerState {
|
|
16
|
+
readonly choices: readonly IChoice[];
|
|
17
|
+
readonly cursor: number;
|
|
18
|
+
readonly chosen: ReadonlySet<string>;
|
|
19
|
+
/** Выбор окончен, `chosen` — ответ. */
|
|
20
|
+
readonly done: boolean;
|
|
21
|
+
/** Выбор брошен: ответа нет, и подставлять вместо него умолчание нельзя. */
|
|
22
|
+
readonly cancelled: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */
|
|
25
|
+
export declare function pickerOf(choices: readonly IChoice[]): IPickerState;
|
|
26
|
+
export declare const KEY_UP: string;
|
|
27
|
+
export declare const KEY_DOWN: string;
|
|
28
|
+
export declare const KEY_ESCAPE: string;
|
|
29
|
+
export declare const KEY_CANCEL: string;
|
|
30
|
+
/**
|
|
31
|
+
* Кусок сырого потока, разобранный на клавиши.
|
|
32
|
+
*
|
|
33
|
+
* Терминал не обещает по куску на нажатие: набранное быстро, вставленное из буфера и всё, что
|
|
34
|
+
* пришло по конвейеру, приезжает одной строкой. Неразобранный кусок читался бы как одна
|
|
35
|
+
* незнакомая клавиша, и выбиралка молча не отзывалась бы ни на что — так она и повисла на
|
|
36
|
+
* первом прогоне через конвейер.
|
|
37
|
+
*
|
|
38
|
+
* Escape-последовательность берётся целиком: `ESC [ A` — это стрелка, а не escape и две буквы.
|
|
39
|
+
*/
|
|
40
|
+
export declare function keysOf(chunk: string): readonly string[];
|
|
41
|
+
/** Переход состояния по одной нажатой клавише. Незнакомая клавиша не делает ничего. */
|
|
42
|
+
export declare function pressKey(state: IPickerState, key: string): IPickerState;
|
|
43
|
+
/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */
|
|
44
|
+
export declare const pressChunk: (state: IPickerState, chunk: string) => IPickerState;
|
|
45
|
+
/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком в скобках. */
|
|
46
|
+
export declare function renderPicker(state: IPickerState, question: string): readonly string[];
|
|
47
|
+
//# sourceMappingURL=picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,OAAO;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAED,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,YAAY,CAElE;AAUD,eAAO,MAAM,MAAM,EAAE,MAAsB,CAAC;AAC5C,eAAO,MAAM,QAAQ,EAAE,MAAsB,CAAC;AAC9C,eAAO,MAAM,UAAU,EAAE,MAAe,CAAC;AACzC,eAAO,MAAM,UAAU,EAAE,MAA+B,CAAC;AAiCzD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAcvD;AAED,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAwBvE;AAED,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,KAAK,YACxB,CAAC;AAK1C,uFAAuF;AACvF,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAarF"}
|