@rt-tools/agent-kit 0.2.0 → 0.4.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 +235 -18
- package/assets/agents/business-analyst.md +74 -0
- package/assets/agents/project-manager.md +70 -0
- package/assets/agents/qa-engineer.md +72 -0
- package/assets/agents/skill-curator.md +110 -0
- package/assets/agents/spec-critic.md +44 -0
- package/assets/agents/spec-writer.md +50 -0
- package/assets/checks/board.github.mjs +286 -0
- package/assets/checks/check-board.github.mjs +188 -0
- package/assets/checks/check-doc-paths.mjs +163 -0
- package/assets/checks/check-dupes.mjs +277 -0
- package/assets/checks/check-lib-layers.mjs +573 -0
- package/assets/checks/check-reuse.mjs +208 -0
- package/assets/checks/check-schema-drift.mjs +186 -0
- package/assets/checks/check-specs.mjs +1007 -0
- package/assets/checks/check-styles.mjs +109 -0
- package/assets/checks/rt-kit-checks.config.mjs +134 -0
- package/assets/checks/task-new.github.mjs +198 -0
- package/assets/commands/skill-curator.md +70 -0
- package/assets/defaults/gate-map.sh +100 -0
- package/assets/defaults/project.sh +179 -0
- package/assets/hooks/browser-device-id.sh +20 -0
- package/assets/hooks/browser-guard-device-id.sh +28 -0
- package/assets/hooks/browser-guard-no-asking.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +18 -0
- package/assets/hooks/browser-guard-no-other-drivers.sh +79 -0
- package/assets/hooks/browser-guard-require-select.sh +54 -0
- package/assets/hooks/commit-msg.sh +26 -0
- package/assets/hooks/constitution-index.sh +43 -0
- package/assets/hooks/dev-server-guard.sh +115 -0
- package/assets/hooks/docs-guard.sh +282 -0
- package/assets/hooks/git-guard-delivery.sh +167 -0
- package/assets/hooks/git-guard-main.sh +73 -0
- package/assets/hooks/git-guard-push-tests.sh +94 -0
- package/assets/hooks/glossary-load.sh +23 -0
- package/assets/hooks/lint-after-edit.sh +219 -0
- package/assets/hooks/qa-dataid-guard.sh +121 -0
- package/assets/hooks/reuse-first-guard.sh +154 -0
- package/assets/hooks/skill-gate-rearm.sh +23 -0
- package/assets/hooks/skill-gate.sh +128 -0
- package/assets/hooks/skill-loaded.sh +21 -0
- package/assets/hooks/sql-guard.sh +679 -0
- package/assets/hooks/task-context-load.sh +100 -0
- package/assets/hooks/task-flow-guard.sh +107 -0
- package/assets/laws/{access.md → application/access.md} +1 -4
- package/assets/laws/{locales.md → application/locales.md} +1 -3
- package/assets/laws/application/money.md +41 -0
- package/assets/laws/application/ownership.md +32 -0
- package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
- package/assets/laws/code-structure.md +7 -6
- package/assets/laws/delivery.md +53 -3
- package/assets/laws/entity-editing.md +49 -55
- package/assets/laws/entity-models.md +4 -14
- package/assets/laws/frontend-application.md +5 -5
- package/assets/laws/lib-imports.md +14 -1
- package/assets/laws/lists.md +33 -0
- package/assets/laws/navigation.md +40 -0
- package/assets/laws/project-documentation.md +17 -8
- package/assets/laws/reuse-first.md +26 -21
- package/assets/laws/shared-code.md +13 -1
- package/assets/laws/verifiability.md +17 -1
- package/assets/laws/work-conduct.md +48 -0
- package/assets/patterns/admin-lists-screen.md +131 -0
- package/assets/patterns/admin-nav-item.md +71 -0
- package/assets/patterns/angular-patterns-state.md +101 -0
- package/assets/patterns/api-layer-pair.md +88 -0
- package/assets/patterns/browser-verification-measure.md +86 -0
- package/assets/patterns/browser-verification-stand.md +143 -0
- package/assets/patterns/component-structure-new.md +99 -0
- package/assets/patterns/dependencies-upgrade.md +65 -0
- package/assets/patterns/doc-style-sweep.md +137 -0
- package/assets/patterns/doc-style-write.md +109 -0
- package/assets/patterns/entity-aside.md +136 -0
- package/assets/patterns/entity-models-new.md +124 -0
- package/assets/patterns/entity-store.md +91 -0
- package/assets/patterns/git-workflow-commit.azure.md +259 -0
- package/assets/patterns/git-workflow-commit.github.md +333 -0
- package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
- package/assets/patterns/git-workflow-merge.md +99 -0
- package/assets/patterns/git-workflow-migration.md +88 -0
- package/assets/patterns/git-workflow-restart.md +49 -0
- package/assets/patterns/lib-layers-move.md +95 -0
- package/assets/patterns/lib-layers-new.md +82 -0
- package/assets/patterns/ownership-scope-resolve.md +69 -0
- package/assets/patterns/permissions-procedure.md +71 -0
- package/assets/patterns/platform-access-di.md +84 -0
- package/assets/patterns/pricing-quote.md +71 -0
- package/assets/patterns/reuse-first-extend.md +73 -0
- package/assets/patterns/seo-page.md +104 -0
- package/assets/patterns/seo-verify.md +83 -0
- package/assets/patterns/shared-code-new.md +86 -0
- package/assets/patterns/spec-driven-domain.md +107 -0
- package/assets/patterns/spec-driven-rule.md +127 -0
- package/assets/patterns/styling-bem-component.md +88 -0
- package/assets/patterns/styling-bem-layout.md +73 -0
- package/assets/patterns/task-flow-close.md +90 -0
- package/assets/patterns/task-flow-resume.md +94 -0
- package/assets/patterns/task-flow-start.md +117 -0
- package/assets/patterns/testing-e2e.md +92 -0
- package/assets/patterns/testing-unit.md +117 -0
- package/assets/patterns/translations-key.md +64 -0
- package/assets/patterns/ts-procedure.md +65 -0
- package/assets/rules/angular-patterns.md +71 -0
- package/assets/rules/api-layer.md +71 -0
- package/assets/rules/browser-verification.md +87 -0
- package/assets/rules/component-structure.md +64 -0
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +103 -0
- package/assets/rules/entity-conventions.md +78 -0
- package/assets/rules/entity-models.md +70 -0
- package/assets/rules/git-workflow.azure.md +116 -0
- package/assets/rules/git-workflow.github.md +123 -0
- package/assets/rules/git-workflow.gitlab.md +113 -0
- package/assets/rules/lib-layers.md +80 -0
- package/assets/rules/lists.md +73 -0
- package/assets/rules/navigation.md +78 -0
- package/assets/rules/ownership-scope.md +63 -0
- package/assets/rules/permissions.md +70 -0
- package/assets/rules/platform-access.md +77 -0
- package/assets/rules/pricing.md +64 -0
- package/assets/rules/reuse-first.md +83 -0
- package/assets/rules/seo.md +71 -0
- package/assets/rules/shared-code.md +70 -0
- package/assets/rules/spec-driven.md +135 -0
- package/assets/rules/styling-bem.md +74 -0
- package/assets/rules/task-flow.md +110 -0
- package/assets/rules/testing.md +100 -0
- package/assets/rules/translations.md +69 -0
- package/assets/rules/typescript-conventions.md +76 -0
- package/assets/skills/agent-kit.md +81 -0
- package/assets/skills/write-a-skill.md +108 -0
- package/assets/templates/gate-map.sh +45 -0
- package/assets/templates/implementation.md +44 -0
- package/assets/templates/pattern.md +5 -1
- package/assets/templates/project.sh +54 -0
- package/assets/templates/rule.md +12 -23
- package/assets/variants.json +20 -0
- package/assets/workflows/feature.js +134 -0
- package/assets/workflows/plan.js +150 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +78 -5
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +5 -0
- package/bin/prompt.d.ts.map +1 -1
- package/bin/prompt.js +19 -7
- package/bin/prompt.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +14 -1
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +23 -2
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +52 -5
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +104 -16
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +22 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +218 -11
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +57 -0
- package/lib/companion.d.ts.map +1 -0
- package/lib/companion.js +60 -0
- package/lib/companion.js.map +1 -0
- package/lib/config.d.ts +42 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +60 -2
- package/lib/config.js.map +1 -1
- package/lib/freshness.d.ts +14 -0
- package/lib/freshness.d.ts.map +1 -0
- package/lib/freshness.js +116 -0
- package/lib/freshness.js.map +1 -0
- package/lib/hooks-map.d.ts +24 -0
- package/lib/hooks-map.d.ts.map +1 -0
- package/lib/hooks-map.js +72 -0
- package/lib/hooks-map.js.map +1 -0
- package/lib/integrity.d.ts +36 -0
- package/lib/integrity.d.ts.map +1 -0
- package/lib/integrity.js +44 -0
- package/lib/integrity.js.map +1 -0
- package/lib/picker.d.ts +11 -1
- package/lib/picker.d.ts.map +1 -1
- package/lib/picker.js +44 -6
- package/lib/picker.js.map +1 -1
- 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 +29 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +78 -4
- package/lib/sync.js.map +1 -1
- package/lib/variants.d.ts +44 -0
- package/lib/variants.d.ts.map +1 -0
- package/lib/variants.js +82 -0
- package/lib/variants.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.4.0.tgz +0 -0
- package/assets/laws/admin-lists.md +0 -35
- package/assets/laws/admin-navigation.md +0 -38
- package/rt-tools-agent-kit-0.2.0.tgz +0 -0
package/lib/commands.js
CHANGED
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
* а `process.exit` зовёт только точка входа — иначе ни одну из них нельзя было бы проверить
|
|
4
4
|
* спекой, не перехватывая поток вывода.
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { existsSync, mkdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { collectAssets } from './assets.js';
|
|
9
9
|
import { isChosen, readCatalog } from './catalog.js';
|
|
10
|
+
import { isUnfilled } from './companion.js';
|
|
10
11
|
import { CONFIG_PATH, DEFAULT_LAYOUT, KINDS, OVERRIDES_DIR, readConfig } from './config.js';
|
|
12
|
+
import { hooksSection, SETTINGS_PATH } from './hooks-map.js';
|
|
11
13
|
import { isRefusal } from './plan.js';
|
|
12
14
|
import { pendingOf, planSync, runSync } from './sync.js';
|
|
15
|
+
import { placeholdersOf } from './vars.js';
|
|
16
|
+
import { readAxes, unansweredAxes } from './variants.js';
|
|
13
17
|
/** Что с файлом сделала раскладка. Прошедшее время здесь правда: `sync` уже записал. */
|
|
14
18
|
const DONE_WORD = {
|
|
15
19
|
create: 'положен',
|
|
@@ -32,56 +36,174 @@ const STATE_WORD = {
|
|
|
32
36
|
};
|
|
33
37
|
const NOT_CHOSEN = 'не выбран';
|
|
34
38
|
const SKIPPED = 'пропущен';
|
|
39
|
+
/** Ресурс чужого вида: в этом дереве его не существует, а не «от него отказались». */
|
|
40
|
+
const OTHER_VARIANT = 'другой вид';
|
|
35
41
|
const KIND_TITLE = {
|
|
36
42
|
laws: 'ЗАКОНЫ',
|
|
43
|
+
rules: 'ПРАВИЛА',
|
|
44
|
+
patterns: 'ПАТТЕРНЫ',
|
|
45
|
+
skills: 'СКИЛЫ',
|
|
37
46
|
hooks: 'ХУКИ',
|
|
47
|
+
defaults: 'УМОЛЧАНИЯ',
|
|
38
48
|
checks: 'ПРОВЕРКИ',
|
|
39
49
|
agents: 'АГЕНТЫ',
|
|
50
|
+
commands: 'КОМАНДЫ',
|
|
51
|
+
workflows: 'ВОРКФЛОУ',
|
|
40
52
|
templates: 'ШАБЛОНЫ',
|
|
41
53
|
};
|
|
54
|
+
/** Состояние компаньона словами. У заполненного слова нет: о нём говорить нечего. */
|
|
55
|
+
const COMPANION_WORD = {
|
|
56
|
+
missing: 'пропал',
|
|
57
|
+
draft: 'остался черновиком',
|
|
58
|
+
filled: 'заполнен',
|
|
59
|
+
};
|
|
42
60
|
const NO_CONFIG = `нет \`${CONFIG_PATH}\` — начни с \`agent-kit init\``;
|
|
61
|
+
/** Куда уезжает прежнее содержимое файла, отданного пакету. */
|
|
62
|
+
export const KEPT_SUFFIX = '.before-rt-kit';
|
|
43
63
|
const holes = (result) => [...result.missing].map(([asset, names]) => ` ${asset}: нет значений для ${names.map((name) => `{{${name}}}`).join(', ')}`);
|
|
64
|
+
const unfilled = (result) => result.companions.filter(isUnfilled);
|
|
65
|
+
/** Ось без ответа: чем её спрашивают и из чего выбирают. */
|
|
66
|
+
const axisLines = (axes) => axes.flatMap((axis) => [
|
|
67
|
+
` ${axis.name} — ${axis.question}`,
|
|
68
|
+
...axis.options.map((option) => ` ${option.value.padEnd(8)} ${option.title}`),
|
|
69
|
+
` выбор пишется в \`variants\` конфига: {"${axis.name}": "${axis.options[0]?.value ?? ''}"}`,
|
|
70
|
+
]);
|
|
71
|
+
/**
|
|
72
|
+
* Брошенное: файл лежит, а ресурс за ним больше не берут. Печатается отдельным списком и с
|
|
73
|
+
* подсказкой — стереть его пакет не вправе, а промолчать не может: агент читает такой файл как
|
|
74
|
+
* действующее правило.
|
|
75
|
+
*/
|
|
76
|
+
const abandonedLines = (result) => result.abandoned.length
|
|
77
|
+
? [
|
|
78
|
+
`лежит от ресурсов, которые больше не берутся: ${result.abandoned.length}`,
|
|
79
|
+
...result.abandoned.map((path) => ` ${path}`),
|
|
80
|
+
' их не стирает никто: рядом может лежать написанное проектом — убирать вручную',
|
|
81
|
+
]
|
|
82
|
+
: [];
|
|
83
|
+
/**
|
|
84
|
+
* Ресурс, у которого нет вида под выбор дерева. Называется и то, чего не хватает, и оба способа
|
|
85
|
+
* это снять: отказ без действия обходят, а не исполняют.
|
|
86
|
+
*/
|
|
87
|
+
const gapLines = (result) => result.gaps.flatMap((gap) => [
|
|
88
|
+
` ${gap.kind}/${gap.name} — есть только под ${gap.axis}: ${gap.available.join(', ')}, а выбран «${gap.chosen}»`,
|
|
89
|
+
` либо заведи вид под «${gap.chosen}», либо назови в skip: ${gap.ids.join(', ')}`,
|
|
90
|
+
]);
|
|
91
|
+
/**
|
|
92
|
+
* Гарды, которых нет в настройке агента, и готовый кусок для неё.
|
|
93
|
+
*
|
|
94
|
+
* Кусок печатается целиком, а не одними именами: правку в свою настройку делает проект, и
|
|
95
|
+
* список «не хватает трёх» заставляет собирать JSON по памяти — там и теряется образец вызова.
|
|
96
|
+
*/
|
|
97
|
+
const unboundLines = (result) => result.unbound.length
|
|
98
|
+
? [
|
|
99
|
+
`гарды разложены, но в \`${SETTINGS_PATH}\` их не зовёт никто: ${result.unbound.length}`,
|
|
100
|
+
...result.unbound.map((binding) => ` ${binding.path} — ${binding.event} ${binding.matcher}`),
|
|
101
|
+
` вставь в \`${SETTINGS_PATH}\` раздел \`hooks\` — готовый кусок ниже:`,
|
|
102
|
+
...JSON.stringify({ hooks: hooksSection(result.unbound) }, null, 4)
|
|
103
|
+
.split('\n')
|
|
104
|
+
.map((line) => ` ${line}`),
|
|
105
|
+
]
|
|
106
|
+
: [];
|
|
44
107
|
const describe = (result) => [
|
|
45
108
|
...holes(result),
|
|
109
|
+
...gapLines(result),
|
|
110
|
+
...unboundLines(result),
|
|
46
111
|
...pendingOf(result).map((entry) => ` ${entry.path} — ${STATE_WORD[entry.outcome]}`),
|
|
112
|
+
...unfilled(result).map((entry) => ` ${entry.path} — ${COMPANION_WORD[entry.state]}`),
|
|
113
|
+
...abandonedLines(result),
|
|
47
114
|
];
|
|
115
|
+
/** Имена дырок во всех ресурсах, которые дерево берёт. Без конфига — ни одной: выбор неизвестен. */
|
|
116
|
+
function placeholdersIn(config, assetsDir) {
|
|
117
|
+
if (!config) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const names = [];
|
|
121
|
+
for (const asset of collectAssets(config, assetsDir)) {
|
|
122
|
+
for (const name of placeholdersOf(asset.text)) {
|
|
123
|
+
if (!names.includes(name)) {
|
|
124
|
+
names.push(name);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return names.sort();
|
|
129
|
+
}
|
|
48
130
|
/**
|
|
49
131
|
* Что проект выбрал при заведении конфига. Пустой список — весь набор; выбор приходит сюда
|
|
50
132
|
* готовым, потому что взять его негде, кроме края: у строки запуска флаг, у терминала вопрос,
|
|
51
133
|
* а у неинтерактивного прогона нет ни того ни другого — и решать это команде не по чину.
|
|
52
134
|
*/
|
|
53
|
-
export function init(root, only = []) {
|
|
135
|
+
export function init(root, only = [], variants = {}, assetsDir = '') {
|
|
54
136
|
const path = join(root, CONFIG_PATH);
|
|
55
137
|
if (existsSync(path)) {
|
|
56
138
|
return { code: 0, lines: [`${CONFIG_PATH} уже есть — оставлен как есть`] };
|
|
57
139
|
}
|
|
58
|
-
const config = { vars: {}, layout: DEFAULT_LAYOUT, only, skip: [] };
|
|
140
|
+
const config = { vars: {}, layout: DEFAULT_LAYOUT, variants, only, skip: [] };
|
|
59
141
|
mkdirSync(join(root, OVERRIDES_DIR), { recursive: true });
|
|
60
142
|
mkdirSync(join(root, CONFIG_PATH, '..'), { recursive: true });
|
|
61
143
|
writeFileSync(path, `${JSON.stringify(config, null, 4)}\n`, 'utf8');
|
|
144
|
+
// Значения, которых пакет ждёт от дерева, называются здесь, а не отказом раскладки: узнать
|
|
145
|
+
// о них на первом же `sync` значит начать установку с ошибки, притом что список известен
|
|
146
|
+
// сразу — он вычитывается из текстов выбранных ресурсов.
|
|
147
|
+
const wanted = assetsDir ? placeholdersIn(readConfig(root), assetsDir) : [];
|
|
62
148
|
return {
|
|
63
149
|
code: 0,
|
|
64
150
|
lines: [
|
|
65
151
|
`заведён ${CONFIG_PATH}`,
|
|
66
152
|
only.length ? `выбрано ресурсов: ${only.length}` : 'выбрано всё, что везёт пакет',
|
|
153
|
+
...Object.entries(variants).map(([axis, value]) => `${axis}: ${value}`),
|
|
67
154
|
`заведён ${OVERRIDES_DIR}/ — надстройки проекта кладутся сюда путём ресурса`,
|
|
68
|
-
|
|
155
|
+
...(wanted.length
|
|
156
|
+
? [
|
|
157
|
+
`значения, которых ждут ресурсы, — впиши их в \`vars\` конфига: ${wanted.length}`,
|
|
158
|
+
...wanted.map((name) => ` {{${name}}}`),
|
|
159
|
+
]
|
|
160
|
+
: []),
|
|
161
|
+
'дальше: `agent-kit sync`, а своё дописывается в `.claude/rt-kit/gate-map.sh` и `project.sh`',
|
|
69
162
|
],
|
|
70
163
|
};
|
|
71
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Отказ на устаревшей сборке. Раскладка из неё положила бы прежнюю редакцию ресурса и назвала
|
|
167
|
+
* это сделанным: неправда дороже отказа — её замечают, когда правленое правило не действует.
|
|
168
|
+
*/
|
|
169
|
+
const staleRefusal = (stale) => ({
|
|
170
|
+
code: 1,
|
|
171
|
+
lines: [
|
|
172
|
+
'раскладка не начата: строка запуска читает собранное, а ресурсы правлены позже',
|
|
173
|
+
` правлено: ${stale.newest}`,
|
|
174
|
+
` исходные ресурсы: ${stale.source}`,
|
|
175
|
+
' собери пакет и повтори',
|
|
176
|
+
],
|
|
177
|
+
});
|
|
72
178
|
export function sync(env, check) {
|
|
73
179
|
const { root, version, assetsDir } = env;
|
|
74
180
|
const config = readConfig(root);
|
|
75
181
|
if (!config) {
|
|
76
182
|
return { code: 1, lines: [NO_CONFIG] };
|
|
77
183
|
}
|
|
184
|
+
if (env.stale) {
|
|
185
|
+
return staleRefusal(env.stale);
|
|
186
|
+
}
|
|
187
|
+
// Ось без ответа отбивает раскладку целиком, а не пропускает свои ресурсы молча: правило
|
|
188
|
+
// поставки бывает в трёх видах, и дерево, не назвавшее свой, осталось бы вовсе без правила
|
|
189
|
+
// поставки — заметить это можно было бы только по тому, что гейт перестал его требовать.
|
|
190
|
+
const unanswered = unansweredAxes(readAxes(assetsDir), config.variants);
|
|
191
|
+
if (unanswered.length) {
|
|
192
|
+
return { code: 1, lines: ['раскладка не начата: не выбран вид', ...axisLines(unanswered)] };
|
|
193
|
+
}
|
|
78
194
|
if (check) {
|
|
79
195
|
const result = planSync(config, root, version, assetsDir);
|
|
80
196
|
const pending = pendingOf(result);
|
|
81
|
-
|
|
197
|
+
// Незаполненный компаньон — такое же расхождение, как отставший файл: правило разложено,
|
|
198
|
+
// а имён этого дерева при нём нет, и агент читает указание, которому некуда примениться.
|
|
199
|
+
const empty = unfilled(result);
|
|
200
|
+
// Гард, которого не зовёт настройка агента, — такое же расхождение, как отставший файл:
|
|
201
|
+
// он разложен, он коммитится, и по дереву его не отличить от работающего.
|
|
202
|
+
const count = result.missing.size + result.gaps.length + pending.length + empty.length + result.unbound.length;
|
|
203
|
+
if (!count) {
|
|
82
204
|
return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`] };
|
|
83
205
|
}
|
|
84
|
-
return { code: 1, lines: [`sync --check: расхождений ${
|
|
206
|
+
return { code: 1, lines: [`sync --check: расхождений ${count}`, ...describe(result)] };
|
|
85
207
|
}
|
|
86
208
|
const result = runSync(config, root, version, assetsDir);
|
|
87
209
|
// Перечисляются только дырки: раскладка не начата, и назвать файл «положенным» значило бы
|
|
@@ -89,6 +211,14 @@ export function sync(env, check) {
|
|
|
89
211
|
if (result.missing.size) {
|
|
90
212
|
return { code: 1, lines: ['раскладка не начата: нечего подставить в дырки', ...holes(result)] };
|
|
91
213
|
}
|
|
214
|
+
// Ресурс без вида под выбор дерева отбивает раскладку целиком, а не выпадает из неё молча:
|
|
215
|
+
// правило, разложенное без инструмента, который оно зовёт, читается как действующее.
|
|
216
|
+
if (result.gaps.length) {
|
|
217
|
+
return {
|
|
218
|
+
code: 1,
|
|
219
|
+
lines: ['раскладка не начата: у этих ресурсов нет вида под выбор дерева', ...gapLines(result)],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
92
222
|
const refused = result.planned.filter((entry) => isRefusal(entry.outcome));
|
|
93
223
|
if (refused.length) {
|
|
94
224
|
return {
|
|
@@ -97,14 +227,74 @@ export function sync(env, check) {
|
|
|
97
227
|
'раскладка не начата: эти файлы пакет переписывать не станет',
|
|
98
228
|
...refused.map((entry) => ` ${entry.path} — ${DONE_WORD[entry.outcome]}`),
|
|
99
229
|
'правку надо либо перенести в надстройку, либо снять — и повторить',
|
|
230
|
+
`положенное не пакетом отдаётся ему командой \`agent-kit adopt\`: прежнее содержимое ляжет рядом с пометкой \`${KEPT_SUFFIX}\``,
|
|
100
231
|
],
|
|
101
232
|
};
|
|
102
233
|
}
|
|
234
|
+
// Раскладка удалась, а гарды могут остаться неподключёнными: настройка агента принадлежит
|
|
235
|
+
// дереву, и пакет в неё не пишет. Код возврата остаётся нулевым — файлы легли, — но молчать
|
|
236
|
+
// об этом нельзя: узнают иначе, когда что-нибудь пройдёт мимо гарда.
|
|
103
237
|
return {
|
|
104
238
|
code: 0,
|
|
105
|
-
lines:
|
|
106
|
-
|
|
107
|
-
|
|
239
|
+
lines: [
|
|
240
|
+
...(result.written.length
|
|
241
|
+
? [`разложено файлов: ${result.written.length}`, ...result.written.map((path) => ` ${path}`)]
|
|
242
|
+
: ['всё уже разложено']),
|
|
243
|
+
...unboundLines(result),
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Отдать пакету файл, который лежит в дереве не от него.
|
|
249
|
+
*
|
|
250
|
+
* Пока команды не было, `sync` отказывал на такой файл поимённо и предлагал «перенести правку в
|
|
251
|
+
* надстройку или снять» — то есть работу, которую делают руками, и до неё же сводилась вся
|
|
252
|
+
* установка в живое дерево. При этом один чужой файл останавливал раскладку целиком.
|
|
253
|
+
*
|
|
254
|
+
* Молчаливой перезаписи здесь нет и быть не может: в файле лежит то, чего в дереве больше нигде
|
|
255
|
+
* нет. Поэтому переход — это переименование прежнего содержимого рядом и повторный `sync`:
|
|
256
|
+
* пакет кладёт своё, а прежнее остаётся на диске под именем с пометкой и ждёт разбора.
|
|
257
|
+
*/
|
|
258
|
+
export function adopt(env, names) {
|
|
259
|
+
const { root, version, assetsDir } = env;
|
|
260
|
+
const config = readConfig(root);
|
|
261
|
+
if (!config) {
|
|
262
|
+
return { code: 1, lines: [NO_CONFIG] };
|
|
263
|
+
}
|
|
264
|
+
const foreign = planSync(config, root, version, assetsDir).planned.filter((entry) => entry.outcome === 'foreign');
|
|
265
|
+
if (!foreign.length) {
|
|
266
|
+
return { code: 0, lines: ['чужих файлов на путях пакета нет'] };
|
|
267
|
+
}
|
|
268
|
+
// Имена принимаются как их печатает `sync`: путём в дереве. Без имён отдаются все — на
|
|
269
|
+
// свежей установке их бывает столько же, сколько ресурсов, и перечислять их руками значит
|
|
270
|
+
// делать ту же работу, ради которой команда заведена.
|
|
271
|
+
const wanted = names.length
|
|
272
|
+
? foreign.filter((entry) => names.some((name) => entry.path.endsWith(name)))
|
|
273
|
+
: foreign;
|
|
274
|
+
if (!wanted.length) {
|
|
275
|
+
return {
|
|
276
|
+
code: 1,
|
|
277
|
+
lines: ['ни одно имя не отвечает чужому файлу; чужие сейчас такие:', ...foreign.map((e) => ` ${e.path}`)],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const moved = [];
|
|
281
|
+
const blocked = [];
|
|
282
|
+
for (const entry of wanted) {
|
|
283
|
+
const path = join(root, entry.path);
|
|
284
|
+
const kept = `${path}${KEPT_SUFFIX}`;
|
|
285
|
+
if (existsSync(kept)) {
|
|
286
|
+
blocked.push(` ${entry.path}${KEPT_SUFFIX} — уже лежит, разбери его прежде`);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
renameSync(path, kept);
|
|
290
|
+
moved.push(` ${entry.path} → ${entry.path}${KEPT_SUFFIX}`);
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
code: blocked.length ? 1 : 0,
|
|
294
|
+
lines: [
|
|
295
|
+
...(moved.length ? [`прежнее содержимое отложено: ${moved.length}`, ...moved, 'дальше: `agent-kit sync`'] : []),
|
|
296
|
+
...(blocked.length ? ['не тронуто:', ...blocked] : []),
|
|
297
|
+
],
|
|
108
298
|
};
|
|
109
299
|
}
|
|
110
300
|
export function doctor(env) {
|
|
@@ -120,12 +310,26 @@ export function doctor(env) {
|
|
|
120
310
|
}
|
|
121
311
|
const catalog = readCatalog(assetsDir);
|
|
122
312
|
const taken = collectAssets(config, assetsDir).length;
|
|
313
|
+
const unanswered = unansweredAxes(readAxes(assetsDir), config.variants);
|
|
314
|
+
const chosen = Object.entries(config.variants).map(([axis, value]) => `${axis}: ${value}`);
|
|
315
|
+
// Ресурс чужого вида в «не выбрано» не идёт: проект от него не отказывался — его в этом
|
|
316
|
+
// дереве не существует, как не существует второго правила поставки.
|
|
317
|
+
const foreignVariant = catalog.filter((entry) => entry.variant !== null && config.variants[entry.variant.axis] !== entry.variant.value);
|
|
318
|
+
const other = foreignVariant.length;
|
|
319
|
+
// Ресурс, названный и в отказе, и чужим видом, считается один раз — иначе «не выбрано»
|
|
320
|
+
// уходит в минус, а минус в отчёте читается как поломка счёта, а не как двойной счёт.
|
|
321
|
+
const skipped = config.skip.filter((id) => foreignVariant.every((entry) => entry.id !== id)).length;
|
|
123
322
|
const lines = [
|
|
124
323
|
`пакет v${version}, везёт ресурсов ${catalog.length}, взято ${taken}`,
|
|
125
|
-
`не выбрано: ${catalog.length - taken -
|
|
324
|
+
`не выбрано: ${catalog.length - taken - skipped - other}, пропущено: ${skipped}, другой вид: ${other}`,
|
|
126
325
|
`значений в конфиге: ${Object.keys(config.vars).length}`,
|
|
326
|
+
...chosen,
|
|
127
327
|
...[...counted].map(([outcome, count]) => `${STATE_WORD[outcome]}: ${count}`),
|
|
128
328
|
...describe(result),
|
|
329
|
+
...(unanswered.length ? ['не выбран вид — раскладка не начнётся:', ...axisLines(unanswered)] : []),
|
|
330
|
+
// Про устаревшую сборку `doctor` говорит, но отказом её не считает: он ничего не пишет,
|
|
331
|
+
// и прочитать состояние дерева можно и из вчерашней сборки — знать бы, что она вчерашняя.
|
|
332
|
+
...(env.stale ? ['собранное отстало от исходников — раскладка откажет:', ` правлено: ${env.stale.newest}`] : []),
|
|
129
333
|
];
|
|
130
334
|
return { code: 0, lines };
|
|
131
335
|
}
|
|
@@ -149,10 +353,13 @@ export function list(env) {
|
|
|
149
353
|
if (!config) {
|
|
150
354
|
return 'везёт пакет';
|
|
151
355
|
}
|
|
356
|
+
if (entry.variant && config.variants[entry.variant.axis] !== entry.variant.value) {
|
|
357
|
+
return OTHER_VARIANT;
|
|
358
|
+
}
|
|
152
359
|
if (config.skip.includes(entry.id)) {
|
|
153
360
|
return SKIPPED;
|
|
154
361
|
}
|
|
155
|
-
if (!isChosen(entry, config
|
|
362
|
+
if (!isChosen(entry, config)) {
|
|
156
363
|
return NOT_CHOSEN;
|
|
157
364
|
}
|
|
158
365
|
const outcome = planned.get(entry.id);
|
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 { IEntryOfCatalog, isChosen, readCatalog } from './catalog.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 hooks: 'ХУКИ',\n checks: 'ПРОВЕРКИ',\n agents: 'АГЕНТЫ',\n templates: 'ШАБЛОНЫ',\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} — ${STATE_WORD[entry.outcome]}`),\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 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} — ${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"]}
|
|
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, renameSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { collectAssets } from './assets.js';\nimport { IEntryOfCatalog, IGapOfVariant, 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 { IStaleBuild } from './freshness.js';\nimport { hooksSection, IHookBinding, SETTINGS_PATH } from './hooks-map.js';\nimport { IPlanned, isRefusal, TOutcome } from './plan.js';\nimport { ISyncResult, pendingOf, planSync, runSync } from './sync.js';\nimport { placeholdersOf } from './vars.js';\nimport { IAxis, IOptionOfAxis, readAxes, unansweredAxes } from './variants.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 * Ресурсы правлены позже, чем собран пакет, из которого идёт раскладка; `null` — сверять не с\n * чем. Считается на краю, где пакет знает своё расположение: библиотечные модули работают с\n * тем каталогом ресурсов, который им дали, и о существовании исходников не знают.\n */\n readonly stale?: IStaleBuild | null;\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 OTHER_VARIANT: string = 'другой вид';\n\nconst KIND_TITLE: Readonly<Record<TKind, string>> = {\n laws: 'ЗАКОНЫ',\n rules: 'ПРАВИЛА',\n patterns: 'ПАТТЕРНЫ',\n skills: 'СКИЛЫ',\n hooks: 'ХУКИ',\n defaults: 'УМОЛЧАНИЯ',\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\n/** Куда уезжает прежнее содержимое файла, отданного пакету. */\nexport const KEPT_SUFFIX: string = '.before-rt-kit';\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\n/** Ось без ответа: чем её спрашивают и из чего выбирают. */\nconst axisLines: (axes: readonly IAxis[]) => string[] = (axes: readonly IAxis[]): string[] =>\n axes.flatMap((axis: IAxis): string[] => [\n ` ${axis.name} — ${axis.question}`,\n ...axis.options.map((option: IOptionOfAxis): string => ` ${option.value.padEnd(8)} ${option.title}`),\n ` выбор пишется в \\`variants\\` конфига: {\"${axis.name}\": \"${axis.options[0]?.value ?? ''}\"}`,\n ]);\n\n/**\n * Брошенное: файл лежит, а ресурс за ним больше не берут. Печатается отдельным списком и с\n * подсказкой — стереть его пакет не вправе, а промолчать не может: агент читает такой файл как\n * действующее правило.\n */\nconst abandonedLines: (result: ISyncResult) => string[] = (result: ISyncResult): string[] =>\n result.abandoned.length\n ? [\n `лежит от ресурсов, которые больше не берутся: ${result.abandoned.length}`,\n ...result.abandoned.map((path: string): string => ` ${path}`),\n ' их не стирает никто: рядом может лежать написанное проектом — убирать вручную',\n ]\n : [];\n\n/**\n * Ресурс, у которого нет вида под выбор дерева. Называется и то, чего не хватает, и оба способа\n * это снять: отказ без действия обходят, а не исполняют.\n */\nconst gapLines: (result: ISyncResult) => string[] = (result: ISyncResult): string[] =>\n result.gaps.flatMap((gap: IGapOfVariant): string[] => [\n ` ${gap.kind}/${gap.name} — есть только под ${gap.axis}: ${gap.available.join(', ')}, а выбран «${gap.chosen}»`,\n ` либо заведи вид под «${gap.chosen}», либо назови в skip: ${gap.ids.join(', ')}`,\n ]);\n\n/**\n * Гарды, которых нет в настройке агента, и готовый кусок для неё.\n *\n * Кусок печатается целиком, а не одними именами: правку в свою настройку делает проект, и\n * список «не хватает трёх» заставляет собирать JSON по памяти — там и теряется образец вызова.\n */\nconst unboundLines: (result: ISyncResult) => string[] = (result: ISyncResult): string[] =>\n result.unbound.length\n ? [\n `гарды разложены, но в \\`${SETTINGS_PATH}\\` их не зовёт никто: ${result.unbound.length}`,\n ...result.unbound.map((binding: IHookBinding): string => ` ${binding.path} — ${binding.event} ${binding.matcher}`),\n ` вставь в \\`${SETTINGS_PATH}\\` раздел \\`hooks\\` — готовый кусок ниже:`,\n ...JSON.stringify({ hooks: hooksSection(result.unbound) }, null, 4)\n .split('\\n')\n .map((line: string): string => ` ${line}`),\n ]\n : [];\n\nconst describe: (result: ISyncResult) => string[] = (result: ISyncResult): string[] => [\n ...holes(result),\n ...gapLines(result),\n ...unboundLines(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 ...abandonedLines(result),\n];\n\n/** Имена дырок во всех ресурсах, которые дерево берёт. Без конфига — ни одной: выбор неизвестен. */\nfunction placeholdersIn(config: IConfig | null, assetsDir: string): readonly string[] {\n if (!config) {\n return [];\n }\n const names: string[] = [];\n for (const asset of collectAssets(config, assetsDir)) {\n for (const name of placeholdersOf(asset.text)) {\n if (!names.includes(name)) {\n names.push(name);\n }\n }\n }\n\n return names.sort();\n}\n\n/**\n * Что проект выбрал при заведении конфига. Пустой список — весь набор; выбор приходит сюда\n * готовым, потому что взять его негде, кроме края: у строки запуска флаг, у терминала вопрос,\n * а у неинтерактивного прогона нет ни того ни другого — и решать это команде не по чину.\n */\nexport function init(\n root: string,\n only: readonly string[] = [],\n variants: Readonly<Record<string, string>> = {},\n assetsDir: string = ''\n): 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, variants, 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 // Значения, которых пакет ждёт от дерева, называются здесь, а не отказом раскладки: узнать\n // о них на первом же `sync` значит начать установку с ошибки, притом что список известен\n // сразу — он вычитывается из текстов выбранных ресурсов.\n const wanted: readonly string[] = assetsDir ? placeholdersIn(readConfig(root), assetsDir) : [];\n\n return {\n code: 0,\n lines: [\n `заведён ${CONFIG_PATH}`,\n only.length ? `выбрано ресурсов: ${only.length}` : 'выбрано всё, что везёт пакет',\n ...Object.entries(variants).map(([axis, value]: [string, string]): string => `${axis}: ${value}`),\n `заведён ${OVERRIDES_DIR}/ — надстройки проекта кладутся сюда путём ресурса`,\n ...(wanted.length\n ? [\n `значения, которых ждут ресурсы, — впиши их в \\`vars\\` конфига: ${wanted.length}`,\n ...wanted.map((name: string): string => ` {{${name}}}`),\n ]\n : []),\n 'дальше: `agent-kit sync`, а своё дописывается в `.claude/rt-kit/gate-map.sh` и `project.sh`',\n ],\n };\n}\n\n/**\n * Отказ на устаревшей сборке. Раскладка из неё положила бы прежнюю редакцию ресурса и назвала\n * это сделанным: неправда дороже отказа — её замечают, когда правленое правило не действует.\n */\nconst staleRefusal: (stale: IStaleBuild) => IOutcomeOfCommand = (stale: IStaleBuild): IOutcomeOfCommand => ({\n code: 1,\n lines: [\n 'раскладка не начата: строка запуска читает собранное, а ресурсы правлены позже',\n ` правлено: ${stale.newest}`,\n ` исходные ресурсы: ${stale.source}`,\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 if (env.stale) {\n return staleRefusal(env.stale);\n }\n\n // Ось без ответа отбивает раскладку целиком, а не пропускает свои ресурсы молча: правило\n // поставки бывает в трёх видах, и дерево, не назвавшее свой, осталось бы вовсе без правила\n // поставки — заметить это можно было бы только по тому, что гейт перестал его требовать.\n const unanswered: readonly IAxis[] = unansweredAxes(readAxes(assetsDir), config.variants);\n if (unanswered.length) {\n return { code: 1, lines: ['раскладка не начата: не выбран вид', ...axisLines(unanswered)] };\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 // Гард, которого не зовёт настройка агента, — такое же расхождение, как отставший файл:\n // он разложен, он коммитится, и по дереву его не отличить от работающего.\n const count: number = result.missing.size + result.gaps.length + pending.length + empty.length + result.unbound.length;\n if (!count) {\n return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`] };\n }\n\n return { code: 1, lines: [`sync --check: расхождений ${count}`, ...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 // Ресурс без вида под выбор дерева отбивает раскладку целиком, а не выпадает из неё молча:\n // правило, разложенное без инструмента, который оно зовёт, читается как действующее.\n if (result.gaps.length) {\n return {\n code: 1,\n lines: ['раскладка не начата: у этих ресурсов нет вида под выбор дерева', ...gapLines(result)],\n };\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 `положенное не пакетом отдаётся ему командой \\`agent-kit adopt\\`: прежнее содержимое ляжет рядом с пометкой \\`${KEPT_SUFFIX}\\``,\n ],\n };\n }\n\n // Раскладка удалась, а гарды могут остаться неподключёнными: настройка агента принадлежит\n // дереву, и пакет в неё не пишет. Код возврата остаётся нулевым — файлы легли, — но молчать\n // об этом нельзя: узнают иначе, когда что-нибудь пройдёт мимо гарда.\n return {\n code: 0,\n lines: [\n ...(result.written.length\n ? [`разложено файлов: ${result.written.length}`, ...result.written.map((path: string): string => ` ${path}`)]\n : ['всё уже разложено']),\n ...unboundLines(result),\n ],\n };\n}\n\n/**\n * Отдать пакету файл, который лежит в дереве не от него.\n *\n * Пока команды не было, `sync` отказывал на такой файл поимённо и предлагал «перенести правку в\n * надстройку или снять» — то есть работу, которую делают руками, и до неё же сводилась вся\n * установка в живое дерево. При этом один чужой файл останавливал раскладку целиком.\n *\n * Молчаливой перезаписи здесь нет и быть не может: в файле лежит то, чего в дереве больше нигде\n * нет. Поэтому переход — это переименование прежнего содержимого рядом и повторный `sync`:\n * пакет кладёт своё, а прежнее остаётся на диске под именем с пометкой и ждёт разбора.\n */\nexport function adopt(env: IEnvironment, names: readonly string[]): 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 foreign: readonly IPlanned[] = planSync(config, root, version, assetsDir).planned.filter(\n (entry: IPlanned): boolean => entry.outcome === 'foreign'\n );\n if (!foreign.length) {\n return { code: 0, lines: ['чужих файлов на путях пакета нет'] };\n }\n\n // Имена принимаются как их печатает `sync`: путём в дереве. Без имён отдаются все — на\n // свежей установке их бывает столько же, сколько ресурсов, и перечислять их руками значит\n // делать ту же работу, ради которой команда заведена.\n const wanted: readonly IPlanned[] = names.length\n ? foreign.filter((entry: IPlanned): boolean => names.some((name: string): boolean => entry.path.endsWith(name)))\n : foreign;\n if (!wanted.length) {\n return {\n code: 1,\n lines: ['ни одно имя не отвечает чужому файлу; чужие сейчас такие:', ...foreign.map((e: IPlanned): string => ` ${e.path}`)],\n };\n }\n\n const moved: string[] = [];\n const blocked: string[] = [];\n for (const entry of wanted) {\n const path: string = join(root, entry.path);\n const kept: string = `${path}${KEPT_SUFFIX}`;\n if (existsSync(kept)) {\n blocked.push(` ${entry.path}${KEPT_SUFFIX} — уже лежит, разбери его прежде`);\n continue;\n }\n renameSync(path, kept);\n moved.push(` ${entry.path} → ${entry.path}${KEPT_SUFFIX}`);\n }\n\n return {\n code: blocked.length ? 1 : 0,\n lines: [\n ...(moved.length ? [`прежнее содержимое отложено: ${moved.length}`, ...moved, 'дальше: `agent-kit sync`'] : []),\n ...(blocked.length ? ['не тронуто:', ...blocked] : []),\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 const unanswered: readonly IAxis[] = unansweredAxes(readAxes(assetsDir), config.variants);\n const chosen: string[] = Object.entries(config.variants).map(([axis, value]: [string, string]): string => `${axis}: ${value}`);\n // Ресурс чужого вида в «не выбрано» не идёт: проект от него не отказывался — его в этом\n // дереве не существует, как не существует второго правила поставки.\n const foreignVariant: readonly IEntryOfCatalog[] = catalog.filter(\n (entry: IEntryOfCatalog): boolean => entry.variant !== null && config.variants[entry.variant.axis] !== entry.variant.value\n );\n const other: number = foreignVariant.length;\n // Ресурс, названный и в отказе, и чужим видом, считается один раз — иначе «не выбрано»\n // уходит в минус, а минус в отчёте читается как поломка счёта, а не как двойной счёт.\n const skipped: number = config.skip.filter((id: string): boolean =>\n foreignVariant.every((entry: IEntryOfCatalog): boolean => entry.id !== id)\n ).length;\n\n const lines: string[] = [\n `пакет v${version}, везёт ресурсов ${catalog.length}, взято ${taken}`,\n `не выбрано: ${catalog.length - taken - skipped - other}, пропущено: ${skipped}, другой вид: ${other}`,\n `значений в конфиге: ${Object.keys(config.vars).length}`,\n ...chosen,\n ...[...counted].map(([outcome, count]: [TOutcome, number]): string => `${STATE_WORD[outcome]}: ${count}`),\n ...describe(result),\n ...(unanswered.length ? ['не выбран вид — раскладка не начнётся:', ...axisLines(unanswered)] : []),\n // Про устаревшую сборку `doctor` говорит, но отказом её не считает: он ничего не пишет,\n // и прочитать состояние дерева можно и из вчерашней сборки — знать бы, что она вчерашняя.\n ...(env.stale ? ['собранное отстало от исходников — раскладка откажет:', ` правлено: ${env.stale.newest}`] : []),\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 (entry.variant && config.variants[entry.variant.axis] !== entry.variant.value) {\n return OTHER_VARIANT;\n }\n if (config.skip.includes(entry.id)) {\n return SKIPPED;\n }\n if (!isChosen(entry, config)) {\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,57 @@
|
|
|
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
|
+
* Статьи правила переносятся в таблицу заранее: их текст — ключ связи, и переписанный руками он
|
|
50
|
+
* расходится с правилом молча. Проекту остаётся вторая колонка, а не перепечатывание первой:
|
|
51
|
+
* двадцать пять компаньонов на первой установке — это работа, и половина её механическая.
|
|
52
|
+
*/
|
|
53
|
+
export declare const draftOf: (template: string, rule: string, text?: string) => string;
|
|
54
|
+
export declare function planCompanion(asset: IAsset, existing: string | null, template: string): ICompanion;
|
|
55
|
+
/** Что `sync --check` считает недоделанным: правило при пустом компаньоне не действует. */
|
|
56
|
+
export declare const isUnfilled: (companion: ICompanion) => boolean;
|
|
57
|
+
//# 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;AAoBjD;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,MAgBxE,CAAC;AAEF,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,60 @@
|
|
|
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
|
+
const STATEMENT_ROW = '| <статья дословно> | `<путь>:<символ>` <!-- заполняет проект --> |';
|
|
16
|
+
/**
|
|
17
|
+
* Статьи правила — жирные фразы, которыми начинается каждый пункт раздела о применении закона.
|
|
18
|
+
* Раздел называется по-разному у правила пакета и у развёрнутого в дереве, поэтому берутся оба.
|
|
19
|
+
*/
|
|
20
|
+
function statementsOf(rule) {
|
|
21
|
+
const section = rule.match(/^## (?:Как закон применяется здесь|Что здесь действует)$([\s\S]*?)(?=^## |$(?![\s\S]))/m);
|
|
22
|
+
if (!section) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
return [...section[1].matchAll(/^- \*\*(.+?)\*\*/gm)].map((found) => found[1].trim());
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Черновик по шаблону пакета. Имя правила подставляется, чтобы заполняющий не гадал, к чему
|
|
29
|
+
* файл относится, — открывают его обычно из гейта, а не из правила.
|
|
30
|
+
*
|
|
31
|
+
* Статьи правила переносятся в таблицу заранее: их текст — ключ связи, и переписанный руками он
|
|
32
|
+
* расходится с правилом молча. Проекту остаётся вторая колонка, а не перепечатывание первой:
|
|
33
|
+
* двадцать пять компаньонов на первой установке — это работа, и половина её механическая.
|
|
34
|
+
*/
|
|
35
|
+
export const draftOf = (template, rule, text = '') => {
|
|
36
|
+
const draft = template.split(RULE_SLOT).join(rule);
|
|
37
|
+
const statements = statementsOf(text);
|
|
38
|
+
if (!statements.length) {
|
|
39
|
+
return draft;
|
|
40
|
+
}
|
|
41
|
+
const rows = statements
|
|
42
|
+
.map((statement) => `| ${statement} | \`<путь>:<символ>\` ${COMPANION_MARK} |`)
|
|
43
|
+
.join('\n');
|
|
44
|
+
return draft.includes(STATEMENT_ROW) ? draft.replace(STATEMENT_ROW, rows) : draft;
|
|
45
|
+
};
|
|
46
|
+
export function planCompanion(asset, existing, template) {
|
|
47
|
+
const path = pathOf(asset);
|
|
48
|
+
if (existing === null) {
|
|
49
|
+
return { rule: asset.name, path, state: 'missing', content: draftOf(template, asset.name, asset.text) };
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
rule: asset.name,
|
|
53
|
+
path,
|
|
54
|
+
state: existing.includes(COMPANION_MARK) ? 'draft' : 'filled',
|
|
55
|
+
content: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Что `sync --check` считает недоделанным: правило при пустом компаньоне не действует. */
|
|
59
|
+
export const isUnfilled = (companion) => companion.state !== 'filled';
|
|
60
|
+
//# 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,wFAAwF;AACxF,MAAM,aAAa,GAAW,qEAAqE,CAAC;AAEpG;;;GAGG;AACH,SAAS,YAAY,CAAC,IAAY;IAC9B,MAAM,OAAO,GAA4B,IAAI,CAAC,KAAK,CAC/C,yFAAyF,CAC5F,CAAC;IACF,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACd,CAAC;IAED,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAuB,EAAU,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACpH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,OAAO,GAA8D,CAC9E,QAAgB,EAChB,IAAY,EACZ,OAAe,EAAE,EACX,EAAE;IACR,MAAM,KAAK,GAAW,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3D,MAAM,UAAU,GAAsB,YAAY,CAAC,IAAI,CAAC,CAAC;IACzD,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,IAAI,GAAW,UAAU;SAC1B,GAAG,CAAC,CAAC,SAAiB,EAAU,EAAE,CAAC,KAAK,SAAS,0BAA0B,cAAc,IAAI,CAAC;SAC9F,IAAI,CAAC,IAAI,CAAC,CAAC;IAEhB,OAAO,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACtF,CAAC,CAAC;AAEF,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,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IAC5G,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/** Строка таблицы «Где исполняются статьи»: статья дословно и пустое место под путь. */\nconst STATEMENT_ROW: string = '| <статья дословно> | `<путь>:<символ>` <!-- заполняет проект --> |';\n\n/**\n * Статьи правила — жирные фразы, которыми начинается каждый пункт раздела о применении закона.\n * Раздел называется по-разному у правила пакета и у развёрнутого в дереве, поэтому берутся оба.\n */\nfunction statementsOf(rule: string): readonly string[] {\n const section: RegExpMatchArray | null = rule.match(\n /^## (?:Как закон применяется здесь|Что здесь действует)$([\\s\\S]*?)(?=^## |$(?![\\s\\S]))/m\n );\n if (!section) {\n return [];\n }\n\n return [...section[1].matchAll(/^- \\*\\*(.+?)\\*\\*/gm)].map((found: RegExpMatchArray): string => found[1].trim());\n}\n\n/**\n * Черновик по шаблону пакета. Имя правила подставляется, чтобы заполняющий не гадал, к чему\n * файл относится, — открывают его обычно из гейта, а не из правила.\n *\n * Статьи правила переносятся в таблицу заранее: их текст — ключ связи, и переписанный руками он\n * расходится с правилом молча. Проекту остаётся вторая колонка, а не перепечатывание первой:\n * двадцать пять компаньонов на первой установке — это работа, и половина её механическая.\n */\nexport const draftOf: (template: string, rule: string, text?: string) => string = (\n template: string,\n rule: string,\n text: string = ''\n): string => {\n const draft: string = template.split(RULE_SLOT).join(rule);\n const statements: readonly string[] = statementsOf(text);\n if (!statements.length) {\n return draft;\n }\n\n const rows: string = statements\n .map((statement: string): string => `| ${statement} | \\`<путь>:<символ>\\` ${COMPANION_MARK} |`)\n .join('\\n');\n\n return draft.includes(STATEMENT_ROW) ? draft.replace(STATEMENT_ROW, rows) : draft;\n};\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, asset.text) };\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"]}
|