@rt-tools/agent-kit 0.8.1 → 0.8.2
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 +24 -19
- package/assets/agents/qa-engineer.md +1 -1
- package/assets/checks/board.github.mjs +56 -17
- package/assets/checks/check-board.github.mjs +49 -5
- package/assets/checks/task-new.github.mjs +33 -5
- package/assets/commands/agent-kit-digest.md +10 -5
- package/assets/commands/next-session.md +4 -4
- package/assets/commands/skill-curator.md +11 -9
- package/assets/defaults/gate-map.sh +11 -4
- package/assets/defaults/project.sh +46 -0
- package/assets/docs/GLOSSARY.md +28 -26
- package/assets/hooks/docs-guard.sh +19 -3
- package/assets/hooks/git-guard-delivery.sh +106 -13
- package/assets/hooks/proposal-guard.sh +93 -0
- package/assets/hooks/reuse-first-guard.sh +68 -14
- package/assets/hooks/skill-gate-layers.sh +1 -1
- package/assets/hooks/skill-gate.sh +26 -0
- package/assets/hooks/task-flow-guard.sh +44 -18
- package/assets/hooks/window-fill-guard.sh +1 -1
- package/assets/laws/delivery.md +13 -7
- package/assets/laws/project-documentation.md +21 -0
- package/assets/laws/verifiability.md +6 -1
- package/assets/laws/work-conduct.md +67 -3
- package/assets/patterns/git-workflow-commit.azure.md +10 -2
- package/assets/patterns/git-workflow-commit.github.md +15 -2
- package/assets/patterns/git-workflow-commit.gitlab.md +10 -2
- package/assets/patterns/git-workflow-merge.md +1 -1
- package/assets/patterns/spec-driven-domain.md +7 -1
- package/assets/patterns/spec-driven-rule.md +6 -0
- package/assets/patterns/task-flow-close.md +78 -9
- package/assets/patterns/task-flow-handoff.md +27 -4
- package/assets/patterns/task-flow-resume.md +23 -5
- package/assets/patterns/task-flow-start.md +5 -1
- package/assets/rules/browser-verification.md +10 -1
- package/assets/rules/doc-style.md +13 -5
- package/assets/rules/git-workflow.azure.md +12 -7
- package/assets/rules/git-workflow.github.md +31 -13
- package/assets/rules/git-workflow.gitlab.md +12 -7
- package/assets/rules/spec-driven.md +4 -0
- package/assets/rules/task-flow.md +47 -22
- package/assets/rules/testing.md +19 -0
- package/assets/rules/typescript-conventions.md +12 -0
- package/assets/skills/agent-kit-extend.md +24 -0
- package/assets/skills/agent-kit.md +54 -6
- package/assets/traits.json +14 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +31 -16
- package/bin/agent-kit.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/argv.d.ts +17 -0
- package/lib/argv.d.ts.map +1 -0
- package/lib/argv.js +44 -0
- package/lib/argv.js.map +1 -0
- package/lib/cargo.d.ts +88 -0
- package/lib/cargo.d.ts.map +1 -0
- package/lib/cargo.js +16 -0
- package/lib/cargo.js.map +1 -0
- package/lib/catalog.d.ts +18 -1
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +12 -2
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +0 -26
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +78 -122
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +37 -0
- package/lib/companion.d.ts.map +1 -1
- package/lib/companion.js +42 -1
- package/lib/companion.js.map +1 -1
- package/lib/config.d.ts +28 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +20 -0
- package/lib/config.js.map +1 -1
- package/lib/ship.d.ts +39 -0
- package/lib/ship.d.ts.map +1 -0
- package/lib/ship.js +87 -0
- package/lib/ship.js.map +1 -0
- package/lib/shipment.d.ts +60 -0
- package/lib/shipment.d.ts.map +1 -0
- package/lib/shipment.js +247 -0
- package/lib/shipment.js.map +1 -0
- package/lib/snapshot.d.ts +30 -0
- package/lib/snapshot.d.ts.map +1 -0
- package/lib/snapshot.js +73 -0
- package/lib/snapshot.js.map +1 -0
- package/lib/traits.d.ts +32 -0
- package/lib/traits.d.ts.map +1 -0
- package/lib/traits.js +82 -0
- package/lib/traits.js.map +1 -0
- package/package.json +6 -2
- package/rt-tools-agent-kit-0.8.2.tgz +0 -0
- package/lib/submit.d.ts +0 -24
- package/lib/submit.d.ts.map +0 -1
- package/lib/submit.js +0 -26
- package/lib/submit.js.map +0 -1
- package/rt-tools-agent-kit-0.8.1.tgz +0 -0
- /package/assets/rules/{entity-conventions.md → entity-conventions.needs-admin.md} +0 -0
- /package/assets/rules/{observability.md → observability.needs-app.md} +0 -0
package/lib/shipment.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Сборка груза и команда отправки.
|
|
3
|
+
*
|
|
4
|
+
* Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без
|
|
5
|
+
* замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево
|
|
6
|
+
* переопределило. Отправка, построенная вокруг предложения, эти данные теряла.
|
|
7
|
+
*
|
|
8
|
+
* Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке
|
|
9
|
+
* подменяется двойником.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { CARGO_SCHEMA_VERSION, } from './cargo.js';
|
|
15
|
+
import { CONFIG_PATH, readConfig } from './config.js';
|
|
16
|
+
import { readObservations, summarize } from './observations.js';
|
|
17
|
+
import { leaksIn, markSent, marksOf, readProposals, TO_PACKAGE } from './proposals.js';
|
|
18
|
+
import { readToken } from './ship.js';
|
|
19
|
+
import { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';
|
|
20
|
+
/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */
|
|
21
|
+
const SLUG_LENGTH = 12;
|
|
22
|
+
/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */
|
|
23
|
+
const REFUSED = 1;
|
|
24
|
+
/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */
|
|
25
|
+
const TOKEN_REFUSED = 401;
|
|
26
|
+
/**
|
|
27
|
+
* Адрес удалённого репозитория, приведённый к одному виду.
|
|
28
|
+
*
|
|
29
|
+
* Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они
|
|
30
|
+
* ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а
|
|
31
|
+
* непривёденные формы дали бы два.
|
|
32
|
+
*/
|
|
33
|
+
export function remoteMarkOf(remote) {
|
|
34
|
+
return remote
|
|
35
|
+
.trim()
|
|
36
|
+
.replace(/\.git$/, '')
|
|
37
|
+
.replace(/^[a-z+]+:\/\//i, '')
|
|
38
|
+
.replace(/^[^@/]+@/, '')
|
|
39
|
+
.replace(/:/g, '/')
|
|
40
|
+
.replace(/\/+$/, '')
|
|
41
|
+
.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.
|
|
45
|
+
*
|
|
46
|
+
* Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих
|
|
47
|
+
* копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе
|
|
48
|
+
* все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.
|
|
49
|
+
*/
|
|
50
|
+
export function treeSlugOf(remote, spoken) {
|
|
51
|
+
const mark = remoteMarkOf(remote);
|
|
52
|
+
return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и
|
|
56
|
+
* наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.
|
|
57
|
+
*
|
|
58
|
+
* Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,
|
|
59
|
+
* а не наблюдения.
|
|
60
|
+
*/
|
|
61
|
+
function knownCounts(counts, known) {
|
|
62
|
+
return counts.filter((count) => known.has(count.name));
|
|
63
|
+
}
|
|
64
|
+
/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */
|
|
65
|
+
export function summaryCargo(summary, tree, overrides, unpicked, known) {
|
|
66
|
+
return {
|
|
67
|
+
schema: CARGO_SCHEMA_VERSION,
|
|
68
|
+
tree,
|
|
69
|
+
days: summary.days,
|
|
70
|
+
sessions: summary.sessions,
|
|
71
|
+
loads: knownCounts(summary.loads, known),
|
|
72
|
+
denials: knownCounts(summary.denials, known),
|
|
73
|
+
// Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`
|
|
74
|
+
// или `browser` — слова пакета, одинаковые у всех деревьев.
|
|
75
|
+
kinds: summary.kinds,
|
|
76
|
+
guards: knownCounts(summary.guards, known),
|
|
77
|
+
unused: summary.unused,
|
|
78
|
+
unpicked,
|
|
79
|
+
overrides,
|
|
80
|
+
versions: summary.versions,
|
|
81
|
+
total: summary.total,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Разборы происшествий, как они лежат на дереве.
|
|
86
|
+
*
|
|
87
|
+
* Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на
|
|
88
|
+
* один вопрос, ради которого его читают.
|
|
89
|
+
*/
|
|
90
|
+
export function readPostmortems(root, dir) {
|
|
91
|
+
const path = join(root, dir);
|
|
92
|
+
if (!existsSync(path)) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
return (readdirSync(path)
|
|
96
|
+
// Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм
|
|
97
|
+
// промаха, — и в приёме встало бы записью, которой нечего сказать.
|
|
98
|
+
.filter((file) => file.endsWith('.md') && file !== 'README.md')
|
|
99
|
+
.sort()
|
|
100
|
+
.map((file) => ({ file, text: readFileSync(join(path, file), 'utf8') })));
|
|
101
|
+
}
|
|
102
|
+
/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */
|
|
103
|
+
export function shipmentsOf(summary, proposals, postmortems) {
|
|
104
|
+
return [
|
|
105
|
+
{ kind: 'сводка', operation: 'summary', body: summary },
|
|
106
|
+
...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),
|
|
107
|
+
...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Что в грузе называет дерево, из которого он уезжает.
|
|
112
|
+
*
|
|
113
|
+
* Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству
|
|
114
|
+
* называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его
|
|
115
|
+
* отправку.
|
|
116
|
+
*/
|
|
117
|
+
export function leaksOfCargo(summary, proposals, marks) {
|
|
118
|
+
const inSummary = leaksIn(JSON.stringify(summary, null, 1), marks).map((leak) => ` сводка: ${leak.why}\n ${leak.text}`);
|
|
119
|
+
const inProposals = proposals.flatMap((entry) => leaksIn(entry.body, marks).map((leak) => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\n ${leak.text}`));
|
|
120
|
+
return [...inSummary, ...inProposals];
|
|
121
|
+
}
|
|
122
|
+
/** Отказ строками. */
|
|
123
|
+
function refusal(...lines) {
|
|
124
|
+
return { code: REFUSED, lines: [...lines] };
|
|
125
|
+
}
|
|
126
|
+
/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */
|
|
127
|
+
function describe(shipment, summary, proposals, postmortems) {
|
|
128
|
+
if (shipment.operation === 'proposals') {
|
|
129
|
+
return `предложений ${proposals.items.length}`;
|
|
130
|
+
}
|
|
131
|
+
if (shipment.operation === 'postmortems') {
|
|
132
|
+
return `разборов ${postmortems.items.length}`;
|
|
133
|
+
}
|
|
134
|
+
return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;
|
|
135
|
+
}
|
|
136
|
+
/** Пометка об отправке: по ней предложение второй раз не уезжает. */
|
|
137
|
+
function markProposals(root, proposals, shipped) {
|
|
138
|
+
const mark = `приём:${shipped.accepted?.month ?? 'принято'}`;
|
|
139
|
+
for (const proposal of proposals) {
|
|
140
|
+
const path = join(root, proposal.file);
|
|
141
|
+
writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт
|
|
146
|
+
* легшего и уже лежавшего.
|
|
147
|
+
*
|
|
148
|
+
* Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже
|
|
149
|
+
* лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку
|
|
150
|
+
* прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.
|
|
151
|
+
*/
|
|
152
|
+
function accepted(shipped) {
|
|
153
|
+
if (!shipped.accepted) {
|
|
154
|
+
return 'принято';
|
|
155
|
+
}
|
|
156
|
+
const record = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;
|
|
157
|
+
const added = shipped.accepted.added;
|
|
158
|
+
return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе
|
|
162
|
+
* увозит половину и не оставляет следа, по которому видно, какую именно.
|
|
163
|
+
*/
|
|
164
|
+
async function send(root, intake, tree, token, ship, going, proposals) {
|
|
165
|
+
const done = [];
|
|
166
|
+
for (const shipment of going) {
|
|
167
|
+
const shipped = await ship(intake, token, shipment);
|
|
168
|
+
if (!shipped.ok) {
|
|
169
|
+
return {
|
|
170
|
+
code: REFUSED,
|
|
171
|
+
lines: [
|
|
172
|
+
...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),
|
|
173
|
+
`${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,
|
|
174
|
+
...(shipped.status === TOKEN_REFUSED
|
|
175
|
+
? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']
|
|
176
|
+
: []),
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
done.push(` ${shipment.kind} → ${accepted(shipped)}`);
|
|
181
|
+
if (shipment.operation === 'proposals') {
|
|
182
|
+
markProposals(root, proposals, shipped);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { code: 0, lines: [`уехало в ${intake}, дерево ${tree}:`, ...done] };
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Отправка груза в приём.
|
|
189
|
+
*
|
|
190
|
+
* Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит
|
|
191
|
+
* отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева
|
|
192
|
+
* незачем.
|
|
193
|
+
*/
|
|
194
|
+
export async function propose(env, options) {
|
|
195
|
+
const { root, assetsDir } = env;
|
|
196
|
+
const config = readConfig(root);
|
|
197
|
+
if (!config) {
|
|
198
|
+
return refusal(`конфига нет: ${CONFIG_PATH} — заводится \`agent-kit init\``);
|
|
199
|
+
}
|
|
200
|
+
if (!config.observe) {
|
|
201
|
+
return {
|
|
202
|
+
code: 0,
|
|
203
|
+
lines: [
|
|
204
|
+
'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',
|
|
205
|
+
'выключатель гасит и отправку целиком, вместе со снимком надстроек',
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (!config.intake) {
|
|
210
|
+
return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \`intake\` в ${CONFIG_PATH}`);
|
|
211
|
+
}
|
|
212
|
+
const tree = treeSlugOf(options.remote, config.tree);
|
|
213
|
+
if (!tree) {
|
|
214
|
+
return refusal('признак дерева не считается: удалённого репозитория нет', `назови его ключом \`tree\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`);
|
|
215
|
+
}
|
|
216
|
+
const token = readToken(root, config.token);
|
|
217
|
+
if (!token) {
|
|
218
|
+
return refusal('токена дерева нет: груз остался неотправленным', `файл с ним называется ключом \`token\` в ${CONFIG_PATH} и лежит вне дерева`, 'выдаётся токен командой приёма — `tree:add` или `tree:token`');
|
|
219
|
+
}
|
|
220
|
+
const read = readObservations(root, options.today, options.days);
|
|
221
|
+
const summary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);
|
|
222
|
+
const cargo = summaryCargo(summary, tree, treeSnapshot(config, assetsDir, root), unpickedOf(config, assetsDir), packagedNames(config, assetsDir));
|
|
223
|
+
const mine = readProposals(root).filter((entry) => entry.address === TO_PACKAGE && !entry.sent);
|
|
224
|
+
const proposals = {
|
|
225
|
+
schema: CARGO_SCHEMA_VERSION,
|
|
226
|
+
tree,
|
|
227
|
+
items: mine.map((entry) => ({ text: entry.body, address: entry.address, resource: entry.resource })),
|
|
228
|
+
};
|
|
229
|
+
const postmortems = { schema: CARGO_SCHEMA_VERSION, tree, items: readPostmortems(root, config.postmortems) };
|
|
230
|
+
const leaked = leaksOfCargo(cargo, mine, marksOf(root, options.remote));
|
|
231
|
+
if (leaked.length) {
|
|
232
|
+
return refusal(`отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`, ...leaked, 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»');
|
|
233
|
+
}
|
|
234
|
+
const going = shipmentsOf(cargo, proposals, postmortems);
|
|
235
|
+
if (options.dryRun) {
|
|
236
|
+
return {
|
|
237
|
+
code: 0,
|
|
238
|
+
lines: [
|
|
239
|
+
`уехало бы в ${config.intake}, дерево ${tree}:`,
|
|
240
|
+
...going.map((shipment) => ` ${shipment.operation} — ${describe(shipment, cargo, proposals, postmortems)}`),
|
|
241
|
+
...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return send(root, config.intake, tree, token, options.ship, going, mine);
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=shipment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shipment.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Сборка груза и команда отправки.\n *\n * Груз уезжает при каждом прогоне, а предложения и разборы — когда они есть: прогон без\n * замечаний тоже говорит, чем пользовались, чем не пользовались ни разу и что дерево\n * переопределило. Отправка, построенная вокруг предложения, эти данные теряла.\n *\n * Собирается груз здесь, а уезжает в `ship.ts`: сборка проверяется вызовом, а сеть в спеке\n * подменяется двойником.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport {\n CARGO_SCHEMA_VERSION,\n ICargoCount,\n ICargoOverride,\n IPostmortemItem,\n IPostmortemsCargo,\n IProposalItem,\n IProposalsCargo,\n ISummaryCargo,\n} from './cargo.js';\nimport { IEnvironment, IOutcomeOfCommand } from './commands.js';\nimport { CONFIG_PATH, IConfig, readConfig } from './config.js';\nimport { IReadResult, ISummary, readObservations, summarize } from './observations.js';\nimport { ILeak, IProposal, leaksIn, markSent, marksOf, readProposals, TO_PACKAGE } from './proposals.js';\nimport { IShipment, IShipped, readToken, TShip } from './ship.js';\nimport { laidOutSkills, packagedNames, treeSnapshot, unpickedOf } from './snapshot.js';\n\n/** Длина признака дерева. Двенадцати знаков хватает, чтобы деревья не сталкивались числом. */\nconst SLUG_LENGTH: number = 12;\n\n/** Отказ команды: именованных кодов у пакета нет — есть код возврата и текст. */\nconst REFUSED: number = 1;\n\n/** Ответ приёма о токене: по нему отказ добавляет строку про отзыв, а не гадает вслух. */\nconst TOKEN_REFUSED: number = 401;\n\n/** Чем отправка живёт: чем отправлять, чем себя выдаёт дерево и за какой отрезок сводка. */\nexport interface IShipOptions {\n /** Печатать, что уехало бы, и не отправлять ничего. */\n readonly dryRun: boolean;\n readonly ship: TShip;\n /** Адрес удалённого репозитория этого дерева: из него считается признак. */\n readonly remote: string;\n /** Сегодняшний день: своих часов у команды нет — иначе сводку за отрезок не проверить спекой. */\n readonly today: string;\n readonly days: number;\n}\n\n/**\n * Адрес удалённого репозитория, приведённый к одному виду.\n *\n * Форм у него несколько — `git@host:владелец/имя.git`, `https://host/владелец/имя`, — и все они\n * ведут к одному дереву. Две рабочие копии одного репозитория обязаны дать один признак, а\n * непривёденные формы дали бы два.\n */\nexport function remoteMarkOf(remote: string): string {\n return remote\n .trim()\n .replace(/\\.git$/, '')\n .replace(/^[a-z+]+:\\/\\//i, '')\n .replace(/^[^@/]+@/, '')\n .replace(/:/g, '/')\n .replace(/\\/+$/, '')\n .toLowerCase();\n}\n\n/**\n * Признак дерева: короткое значение, различающее деревья и не выдающее их адреса.\n *\n * Считается хешем от адреса репозитория: по нему адрес не восстанавливается, а у двух рабочих\n * копий одного репозитория он один. Репозитория нет — берётся то, что назвала настройка: иначе\n * все такие деревья слились бы в одно, и число деревьев в сводке стало бы неправдой молча.\n */\nexport function treeSlugOf(remote: string, spoken: string): string {\n const mark: string = remoteMarkOf(remote);\n\n return mark ? createHash('sha256').update(mark, 'utf8').digest('hex').slice(0, SLUG_LENGTH) : spoken;\n}\n\n/**\n * Счётчики по ресурсам пакета. Имя, которого пакет не знает, — имя правила самого дерева, и\n * наружу оно не едет: дерево называет свои правила по своим доменам и по себе самому.\n *\n * Число событий при этом остаётся полным: считается их столько, сколько было, — отброшены имена,\n * а не наблюдения.\n */\nfunction knownCounts(counts: readonly ICargoCount[], known: ReadonlySet<string>): readonly ICargoCount[] {\n return counts.filter((count: ICargoCount): boolean => known.has(count.name));\n}\n\n/** Сводка наблюдений вместе со снимком надстроек — то, что уезжает первым запросом. */\nexport function summaryCargo(\n summary: ISummary,\n tree: string,\n overrides: readonly ICargoOverride[],\n unpicked: readonly string[],\n known: ReadonlySet<string>\n): ISummaryCargo {\n return {\n schema: CARGO_SCHEMA_VERSION,\n tree,\n days: summary.days,\n sessions: summary.sessions,\n loads: knownCounts(summary.loads, known),\n denials: knownCounts(summary.denials, known),\n // Род правки, на котором гейт отбивал, ресурсом не является: это `extension`, `command`\n // или `browser` — слова пакета, одинаковые у всех деревьев.\n kinds: summary.kinds,\n guards: knownCounts(summary.guards, known),\n unused: summary.unused,\n unpicked,\n overrides,\n versions: summary.versions,\n total: summary.total,\n };\n}\n\n/**\n * Разборы происшествий, как они лежат на дереве.\n *\n * Текст едет целиком: разбор объясняет механизм промаха, и шапка без механизма не отвечает ни на\n * один вопрос, ради которого его читают.\n */\nexport function readPostmortems(root: string, dir: string): IPostmortemItem[] {\n const path: string = join(root, dir);\n\n if (!existsSync(path)) {\n return [];\n }\n\n return (\n readdirSync(path)\n // Описание каталога разбором не является: оно объясняет, что здесь лежит, а не механизм\n // промаха, — и в приёме встало бы записью, которой нечего сказать.\n .filter((file: string): boolean => file.endsWith('.md') && file !== 'README.md')\n .sort()\n .map((file: string): IPostmortemItem => ({ file, text: readFileSync(join(path, file), 'utf8') }))\n );\n}\n\n/** Что уезжает и в каком порядке: сводка первой — ею заводится запись месяца. */\nexport function shipmentsOf(summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): readonly IShipment[] {\n return [\n { kind: 'сводка', operation: 'summary', body: summary },\n ...(proposals.items.length ? [{ kind: 'предложения', operation: 'proposals', body: proposals }] : []),\n ...(postmortems.items.length ? [{ kind: 'разборы', operation: 'postmortems', body: postmortems }] : []),\n ];\n}\n\n/**\n * Что в грузе называет дерево, из которого он уезжает.\n *\n * Проверка накрывает сводку и предложения, но не разбор происшествия: разбор по устройству\n * называет файлы дерева, где промах случился, и накрытая проверка отбивала бы каждую его\n * отправку.\n */\nexport function leaksOfCargo(summary: ISummaryCargo, proposals: readonly IProposal[], marks: readonly string[]): string[] {\n const inSummary: string[] = leaksIn(JSON.stringify(summary, null, 1), marks).map(\n (leak: ILeak): string => ` сводка: ${leak.why}\\n ${leak.text}`\n );\n const inProposals: string[] = proposals.flatMap((entry: IProposal): string[] =>\n leaksIn(entry.body, marks).map(\n (leak: ILeak): string => ` ${entry.file}:${entry.line + leak.line} — ${leak.why}\\n ${leak.text}`\n )\n );\n\n return [...inSummary, ...inProposals];\n}\n\n/** Отказ строками. */\nfunction refusal(...lines: readonly string[]): IOutcomeOfCommand {\n return { code: REFUSED, lines: [...lines] };\n}\n\n/** Что в отправляемом запросе, одной строкой: её читает человек перед тем, как отправить. */\nfunction describe(shipment: IShipment, summary: ISummaryCargo, proposals: IProposalsCargo, postmortems: IPostmortemsCargo): string {\n if (shipment.operation === 'proposals') {\n return `предложений ${proposals.items.length}`;\n }\n if (shipment.operation === 'postmortems') {\n return `разборов ${postmortems.items.length}`;\n }\n\n return `наблюдений ${summary.total} за ${summary.days} дн., надстроек ${summary.overrides.length}, невыбранного ${summary.unpicked.length}`;\n}\n\n/** Пометка об отправке: по ней предложение второй раз не уезжает. */\nfunction markProposals(root: string, proposals: readonly IProposal[], shipped: IShipped): void {\n const mark: string = `приём:${shipped.accepted?.month ?? 'принято'}`;\n\n for (const proposal of proposals) {\n const path: string = join(root, proposal.file);\n writeFileSync(path, markSent(readFileSync(path, 'utf8'), proposal, mark), 'utf8');\n }\n}\n\n/**\n * Что приём сказал о принятом: месяц записи, судьба самой записи и — у предложений — счёт\n * легшего и уже лежавшего.\n *\n * Без счёта строка одинакова и у прогона, привёзшего новое, и у прогона, у которого всё уже\n * лежало: человек читает второе как первое. Приём, счёта не приславший, оставляет строку\n * прежней — так же читаются сводка и разборы, у которых счёта нет вовсе.\n */\nfunction accepted(shipped: IShipped): string {\n if (!shipped.accepted) {\n return 'принято';\n }\n\n const record: string = `${shipped.accepted.month}${shipped.accepted.created ? ', запись заведена' : ', запись дописана'}`;\n const added: number | undefined = shipped.accepted.added;\n\n return added === undefined ? record : `${record}, принято ${added}, уже лежало ${shipped.accepted.known ?? 0}`;\n}\n\n/**\n * Сами запросы. Отправленное помечается сразу, а не после всех: оборвавшаяся отправка иначе\n * увозит половину и не оставляет следа, по которому видно, какую именно.\n */\nasync function send(\n root: string,\n intake: string,\n tree: string,\n token: string,\n ship: TShip,\n going: readonly IShipment[],\n proposals: readonly IProposal[]\n): Promise<IOutcomeOfCommand> {\n const done: string[] = [];\n\n for (const shipment of going) {\n const shipped: IShipped = await ship(intake, token, shipment);\n\n if (!shipped.ok) {\n return {\n code: REFUSED,\n lines: [\n ...(done.length ? [`уехало до отказа: ${done.length}`, ...done] : ['не уехало ничего']),\n `${shipment.kind}: ${intake} ответил ${shipped.status || 'молчанием'} — ${shipped.said}`,\n ...(shipped.status === TOKEN_REFUSED\n ? ['токен не принят: он отозван или заведён не тот — выдай новый командой приёма']\n : []),\n ],\n };\n }\n\n done.push(` ${shipment.kind} → ${accepted(shipped)}`);\n\n if (shipment.operation === 'proposals') {\n markProposals(root, proposals, shipped);\n }\n }\n\n return { code: 0, lines: [`уехало в ${intake}, дерево ${tree}:`, ...done] };\n}\n\n/**\n * Отправка груза в приём.\n *\n * Порядок отказов не случаен: выключенная запись отвечает нулём и молчит про адрес — она гасит\n * отправку целиком, вместе со снимком надстроек, и спрашивать про адрес у выключенного дерева\n * незачем.\n */\nexport async function propose(env: IEnvironment, options: IShipOptions): Promise<IOutcomeOfCommand> {\n const { root, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n\n if (!config) {\n return refusal(`конфига нет: ${CONFIG_PATH} — заводится \\`agent-kit init\\``);\n }\n if (!config.observe) {\n return {\n code: 0,\n lines: [\n 'запись наблюдений выключена ключом `observe` в конфиге — наружу не ушло ничего',\n 'выключатель гасит и отправку целиком, вместе со снимком надстроек',\n ],\n };\n }\n if (!config.intake) {\n return refusal('отправлять некуда: адреса приёма нет', `он объявляется ключом \\`intake\\` в ${CONFIG_PATH}`);\n }\n\n const tree: string = treeSlugOf(options.remote, config.tree);\n if (!tree) {\n return refusal(\n 'признак дерева не считается: удалённого репозитория нет',\n `назови его ключом \\`tree\\` в ${CONFIG_PATH} — иначе деревья без репозитория сольются в одно`\n );\n }\n\n const token: string = readToken(root, config.token);\n if (!token) {\n return refusal(\n 'токена дерева нет: груз остался неотправленным',\n `файл с ним называется ключом \\`token\\` в ${CONFIG_PATH} и лежит вне дерева`,\n 'выдаётся токен командой приёма — `tree:add` или `tree:token`'\n );\n }\n\n const read: IReadResult = readObservations(root, options.today, options.days);\n const summary: ISummary = summarize(read.observations, laidOutSkills(config, assetsDir), options.days);\n const cargo: ISummaryCargo = summaryCargo(\n summary,\n tree,\n treeSnapshot(config, assetsDir, root),\n unpickedOf(config, assetsDir),\n packagedNames(config, assetsDir)\n );\n\n const mine: readonly IProposal[] = readProposals(root).filter(\n (entry: IProposal): boolean => entry.address === TO_PACKAGE && !entry.sent\n );\n const proposals: IProposalsCargo = {\n schema: CARGO_SCHEMA_VERSION,\n tree,\n items: mine.map((entry: IProposal): IProposalItem => ({ text: entry.body, address: entry.address, resource: entry.resource })),\n };\n const postmortems: IPostmortemsCargo = { schema: CARGO_SCHEMA_VERSION, tree, items: readPostmortems(root, config.postmortems) };\n\n const leaked: readonly string[] = leaksOfCargo(cargo, mine, marksOf(root, options.remote));\n if (leaked.length) {\n return refusal(\n `отправка не начата: в грузе назван адрес этого дерева — ${leaked.length}`,\n ...leaked,\n 'найденное отбивает отправку целиком: «уехало два из трёх» читается как «всё в порядке»'\n );\n }\n\n const going: readonly IShipment[] = shipmentsOf(cargo, proposals, postmortems);\n\n if (options.dryRun) {\n return {\n code: 0,\n lines: [\n `уехало бы в ${config.intake}, дерево ${tree}:`,\n ...going.map(\n (shipment: IShipment): string => ` ${shipment.operation} — ${describe(shipment, cargo, proposals, postmortems)}`\n ),\n ...(read.silent ? ['наблюдений не велось ни разу — сводка уезжает снимком надстроек'] : []),\n ],\n };\n }\n\n return send(root, config.intake, tree, token, options.ship, going, mine);\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ICargoOverride } from './cargo.js';
|
|
2
|
+
import { IConfig } from './config.js';
|
|
3
|
+
/** Имена скилов, разложенных в это дерево: правила, паттерны и скилы без закона. */
|
|
4
|
+
export declare function laidOutSkills(config: IConfig, assetsDir: string): readonly string[];
|
|
5
|
+
/**
|
|
6
|
+
* Имена всех ресурсов, которые дерево взяло у пакета: правил, паттернов, гардов, проверок.
|
|
7
|
+
*
|
|
8
|
+
* По ним отбирается то, что уезжает счётчиками. Имя, которого пакет не знает, принадлежит
|
|
9
|
+
* правилу самого дерева — а имена своих правил дерево придумывает по своим доменам и по себе
|
|
10
|
+
* самому, и в сводке, которую читает пакет, им делать нечего.
|
|
11
|
+
*/
|
|
12
|
+
export declare function packagedNames(config: IConfig, assetsDir: string): ReadonlySet<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Правки надстройки над одним ресурсом.
|
|
15
|
+
*
|
|
16
|
+
* Три случая слияния — те же, что и при раскладке: совпавший заголовок замещает раздел пакета,
|
|
17
|
+
* новый дописывается, пустой снимает пакетный. Четвёртый — новый заголовок с пустым телом —
|
|
18
|
+
* правкой не считается: слияние его отбрасывает, и снимок обязан говорить то же самое.
|
|
19
|
+
*/
|
|
20
|
+
export declare function overridesOf(resource: string, packaged: string, override: string): ICargoOverride[];
|
|
21
|
+
/** Что дерево держит поверх текста пакета — по всем ресурсам, которые оно разложило. */
|
|
22
|
+
export declare function treeSnapshot(config: IConfig, assetsDir: string, root: string): ICargoOverride[];
|
|
23
|
+
/**
|
|
24
|
+
* Ресурсы пакета, которых дерево не разложило вовсе.
|
|
25
|
+
*
|
|
26
|
+
* Отказ дерева от ресурса — такой же ответ о тексте пакета, как правка его раздела: без
|
|
27
|
+
* невыбранного мёртвый ресурс неотличим от того, о котором дерево не знало.
|
|
28
|
+
*/
|
|
29
|
+
export declare function unpickedOf(config: IConfig, assetsDir: string): string[];
|
|
30
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/snapshot.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,OAAO,EAA8B,MAAM,aAAa,CAAC;AAGlE,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAInF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAErF;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,EAAE,CAclG;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CAM/F;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAKvE"}
|
package/lib/snapshot.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Снимок надстроек и невыбранное: что дерево сделало с текстом пакета.
|
|
3
|
+
*
|
|
4
|
+
* Снимок — состояние, а не событие: он берётся раскладкой в момент запроса, а не собирается по
|
|
5
|
+
* записям о том, когда надстройку завели. История «когда завели» не меняет ни одного решения —
|
|
6
|
+
* надстройка живёт месяцами, — а событие при раскладке завело бы второй источник той же правды.
|
|
7
|
+
*
|
|
8
|
+
* Наружу уезжают ресурс, раздел и род правки, но не содержимое: текст дописанного пишет дерево
|
|
9
|
+
* само, и в нём стоят его домены, пути и имена — то самое, чего наблюдение не выносит наружу ни
|
|
10
|
+
* при каких событиях. Заголовок своего раздела не уезжает тоже: заголовок раздела пакета
|
|
11
|
+
* одинаков везде, где стоит слой правил, а придуманный деревом — его слова.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { collectAssets } from './assets.js';
|
|
16
|
+
import { chosenEntries, readCatalog } from './catalog.js';
|
|
17
|
+
import { OVERRIDES_DIR, SKILL_KINDS } from './config.js';
|
|
18
|
+
import { parseDocument } from './sections.js';
|
|
19
|
+
/** Имена скилов, разложенных в это дерево: правила, паттерны и скилы без закона. */
|
|
20
|
+
export function laidOutSkills(config, assetsDir) {
|
|
21
|
+
return collectAssets(config, assetsDir)
|
|
22
|
+
.filter((asset) => SKILL_KINDS.includes(asset.kind))
|
|
23
|
+
.map((asset) => asset.name);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Имена всех ресурсов, которые дерево взяло у пакета: правил, паттернов, гардов, проверок.
|
|
27
|
+
*
|
|
28
|
+
* По ним отбирается то, что уезжает счётчиками. Имя, которого пакет не знает, принадлежит
|
|
29
|
+
* правилу самого дерева — а имена своих правил дерево придумывает по своим доменам и по себе
|
|
30
|
+
* самому, и в сводке, которую читает пакет, им делать нечего.
|
|
31
|
+
*/
|
|
32
|
+
export function packagedNames(config, assetsDir) {
|
|
33
|
+
return new Set(collectAssets(config, assetsDir).map((asset) => asset.name));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Правки надстройки над одним ресурсом.
|
|
37
|
+
*
|
|
38
|
+
* Три случая слияния — те же, что и при раскладке: совпавший заголовок замещает раздел пакета,
|
|
39
|
+
* новый дописывается, пустой снимает пакетный. Четвёртый — новый заголовок с пустым телом —
|
|
40
|
+
* правкой не считается: слияние его отбрасывает, и снимок обязан говорить то же самое.
|
|
41
|
+
*/
|
|
42
|
+
export function overridesOf(resource, packaged, override) {
|
|
43
|
+
const base = parseDocument(packaged);
|
|
44
|
+
const own = parseDocument(override);
|
|
45
|
+
const headings = new Set(base.sections.map((section) => section.heading));
|
|
46
|
+
return own.sections
|
|
47
|
+
.filter((section) => headings.has(section.heading) || Boolean(section.body))
|
|
48
|
+
.map((section) => {
|
|
49
|
+
if (!headings.has(section.heading)) {
|
|
50
|
+
return { resource, section: null, kind: 'append' };
|
|
51
|
+
}
|
|
52
|
+
return { resource, section: section.heading, kind: section.body ? 'replace' : 'drop' };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Что дерево держит поверх текста пакета — по всем ресурсам, которые оно разложило. */
|
|
56
|
+
export function treeSnapshot(config, assetsDir, root) {
|
|
57
|
+
return chosenEntries(readCatalog(assetsDir), config).flatMap((entry) => {
|
|
58
|
+
const path = join(root, OVERRIDES_DIR, entry.id);
|
|
59
|
+
return existsSync(path) ? overridesOf(entry.id, entry.text, readFileSync(path, 'utf8')) : [];
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Ресурсы пакета, которых дерево не разложило вовсе.
|
|
64
|
+
*
|
|
65
|
+
* Отказ дерева от ресурса — такой же ответ о тексте пакета, как правка его раздела: без
|
|
66
|
+
* невыбранного мёртвый ресурс неотличим от того, о котором дерево не знало.
|
|
67
|
+
*/
|
|
68
|
+
export function unpickedOf(config, assetsDir) {
|
|
69
|
+
const catalog = readCatalog(assetsDir);
|
|
70
|
+
const taken = new Set(chosenEntries(catalog, config).map((entry) => entry.id));
|
|
71
|
+
return catalog.filter((entry) => !taken.has(entry.id)).map((entry) => entry.id);
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Снимок надстроек и невыбранное: что дерево сделало с текстом пакета.\n *\n * Снимок — состояние, а не событие: он берётся раскладкой в момент запроса, а не собирается по\n * записям о том, когда надстройку завели. История «когда завели» не меняет ни одного решения —\n * надстройка живёт месяцами, — а событие при раскладке завело бы второй источник той же правды.\n *\n * Наружу уезжают ресурс, раздел и род правки, но не содержимое: текст дописанного пишет дерево\n * само, и в нём стоят его домены, пути и имена — то самое, чего наблюдение не выносит наружу ни\n * при каких событиях. Заголовок своего раздела не уезжает тоже: заголовок раздела пакета\n * одинаков везде, где стоит слой правил, а придуманный деревом — его слова.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { collectAssets, IAsset } from './assets.js';\nimport { ICargoOverride } from './cargo.js';\nimport { chosenEntries, IEntryOfCatalog, readCatalog } from './catalog.js';\nimport { IConfig, OVERRIDES_DIR, SKILL_KINDS } from './config.js';\nimport { IDocument, ISection, parseDocument } from './sections.js';\n\n/** Имена скилов, разложенных в это дерево: правила, паттерны и скилы без закона. */\nexport function laidOutSkills(config: IConfig, assetsDir: string): readonly string[] {\n return collectAssets(config, assetsDir)\n .filter((asset: IAsset): boolean => SKILL_KINDS.includes(asset.kind))\n .map((asset: IAsset): string => asset.name);\n}\n\n/**\n * Имена всех ресурсов, которые дерево взяло у пакета: правил, паттернов, гардов, проверок.\n *\n * По ним отбирается то, что уезжает счётчиками. Имя, которого пакет не знает, принадлежит\n * правилу самого дерева — а имена своих правил дерево придумывает по своим доменам и по себе\n * самому, и в сводке, которую читает пакет, им делать нечего.\n */\nexport function packagedNames(config: IConfig, assetsDir: string): ReadonlySet<string> {\n return new Set(collectAssets(config, assetsDir).map((asset: IAsset): string => asset.name));\n}\n\n/**\n * Правки надстройки над одним ресурсом.\n *\n * Три случая слияния — те же, что и при раскладке: совпавший заголовок замещает раздел пакета,\n * новый дописывается, пустой снимает пакетный. Четвёртый — новый заголовок с пустым телом —\n * правкой не считается: слияние его отбрасывает, и снимок обязан говорить то же самое.\n */\nexport function overridesOf(resource: string, packaged: string, override: string): ICargoOverride[] {\n const base: IDocument = parseDocument(packaged);\n const own: IDocument = parseDocument(override);\n const headings: ReadonlySet<string> = new Set(base.sections.map((section: ISection): string => section.heading));\n\n return own.sections\n .filter((section: ISection): boolean => headings.has(section.heading) || Boolean(section.body))\n .map((section: ISection): ICargoOverride => {\n if (!headings.has(section.heading)) {\n return { resource, section: null, kind: 'append' };\n }\n\n return { resource, section: section.heading, kind: section.body ? 'replace' : 'drop' };\n });\n}\n\n/** Что дерево держит поверх текста пакета — по всем ресурсам, которые оно разложило. */\nexport function treeSnapshot(config: IConfig, assetsDir: string, root: string): ICargoOverride[] {\n return chosenEntries(readCatalog(assetsDir), config).flatMap((entry: IEntryOfCatalog): ICargoOverride[] => {\n const path: string = join(root, OVERRIDES_DIR, entry.id);\n\n return existsSync(path) ? overridesOf(entry.id, entry.text, readFileSync(path, 'utf8')) : [];\n });\n}\n\n/**\n * Ресурсы пакета, которых дерево не разложило вовсе.\n *\n * Отказ дерева от ресурса — такой же ответ о тексте пакета, как правка его раздела: без\n * невыбранного мёртвый ресурс неотличим от того, о котором дерево не знало.\n */\nexport function unpickedOf(config: IConfig, assetsDir: string): string[] {\n const catalog: readonly IEntryOfCatalog[] = readCatalog(assetsDir);\n const taken: ReadonlySet<string> = new Set(chosenEntries(catalog, config).map((entry: IEntryOfCatalog): string => entry.id));\n\n return catalog.filter((entry: IEntryOfCatalog): boolean => !taken.has(entry.id)).map((entry: IEntryOfCatalog): string => entry.id);\n}\n"]}
|
package/lib/traits.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface ITrait {
|
|
2
|
+
/** Значение, которое дерево пишет в настройку и которое стоит в имени файла после `needs-`. */
|
|
3
|
+
readonly value: string;
|
|
4
|
+
/** Что это свойство означает — им человек и выбирает, что писать про своё дерево. */
|
|
5
|
+
readonly title: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const TRAITS_FILE: string;
|
|
8
|
+
/** Приставка требования в имени файла. Ею требование и отличается от вида. */
|
|
9
|
+
export declare const REQUIREMENT_PREFIX: string;
|
|
10
|
+
/** Свойства, объявленные пакетом. Нет файла — свойств нет, и требовать нечего. */
|
|
11
|
+
export declare function readTraits(assetsDir: string): readonly ITrait[];
|
|
12
|
+
/**
|
|
13
|
+
* Свойство, которого ресурс требует именем файла, или `null`, если требования нет.
|
|
14
|
+
*
|
|
15
|
+
* Объявленность свойства здесь не проверяется намеренно: незнакомое свойство — опечатка, и о
|
|
16
|
+
* ней говорят вслух отказом раскладки. Считай эта функция такое имя ресурсом без требования,
|
|
17
|
+
* ресурс лёг бы всякому дереву, а опечатку не заметил бы никто.
|
|
18
|
+
*/
|
|
19
|
+
export declare function requirementOf(file: string): string | null;
|
|
20
|
+
/** Имя файла без требования: под ним ресурс и ложится в дерево. */
|
|
21
|
+
export declare function withoutRequirement(file: string, requirement: string | null): string;
|
|
22
|
+
/**
|
|
23
|
+
* Отвечает ли дерево на требование ресурса.
|
|
24
|
+
*
|
|
25
|
+
* Ресурс без требования берётся всегда. Требующий — только когда дерево назвало это свойство
|
|
26
|
+
* своим: молчание требованию не отвечает. Класть наугад дороже — пустой компаньон возвращает
|
|
27
|
+
* ровно ту работу, ради которой требования и заведены.
|
|
28
|
+
*/
|
|
29
|
+
export declare function answersRequirement(requirement: string | null, has: readonly string[]): boolean;
|
|
30
|
+
/** Названные свойства, которых пакет не объявлял, — опечатки, и о них говорят по именам. */
|
|
31
|
+
export declare function unknownTraits(named: readonly string[], traits: readonly ITrait[]): readonly string[];
|
|
32
|
+
//# sourceMappingURL=traits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traits.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/traits.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,MAAM;IACnB,+FAA+F;IAC/F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,WAAW,EAAE,MAAsB,CAAC;AAEjD,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,EAAE,MAAiB,CAAC;AAEnD,kFAAkF;AAClF,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAe/D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYzD;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAQnF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAE9F;AAED,4FAA4F;AAC5F,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAIpG"}
|
package/lib/traits.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Свойства дерева и требования ресурсов к ним.
|
|
3
|
+
*
|
|
4
|
+
* Часть правил верна только там, где есть хранилище, админка или процедуры: наблюдаемость
|
|
5
|
+
* говорит про строки лога и номер обращения, владеющая сущность захода — про запись, которой
|
|
6
|
+
* заход принадлежит. Дереву, которое публикует библиотеки, они не нужны ни одной статьёй, и
|
|
7
|
+
* положенные ему — не долг, а лишние файлы: заполнить их компаньоны нечем.
|
|
8
|
+
*
|
|
9
|
+
* От оси различия это отличается вопросом. Ось спрашивает «какой из трёх» и выбирает ровно
|
|
10
|
+
* один вид; здесь вопрос «есть ли», и таких вещей у одного дерева сразу несколько: дерево,
|
|
11
|
+
* которое публикует пакеты и держит приложение, одним значением не описывается.
|
|
12
|
+
*
|
|
13
|
+
* Требование стоит в имени файла приставкой `needs-`: `observability.needs-db.md`. Приставка —
|
|
14
|
+
* то, чем требование отличимо от вида: `github` читается перечнем осей, `needs-db` — перечнем
|
|
15
|
+
* свойств, и перепутать их нельзя.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
export const TRAITS_FILE = 'traits.json';
|
|
20
|
+
/** Приставка требования в имени файла. Ею требование и отличается от вида. */
|
|
21
|
+
export const REQUIREMENT_PREFIX = 'needs-';
|
|
22
|
+
/** Свойства, объявленные пакетом. Нет файла — свойств нет, и требовать нечего. */
|
|
23
|
+
export function readTraits(assetsDir) {
|
|
24
|
+
let raw;
|
|
25
|
+
try {
|
|
26
|
+
raw = JSON.parse(readFileSync(join(assetsDir, TRAITS_FILE), 'utf8'));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
return Object.entries(raw).map(([value, trait]) => ({
|
|
35
|
+
value,
|
|
36
|
+
title: trait.title ?? '',
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Свойство, которого ресурс требует именем файла, или `null`, если требования нет.
|
|
41
|
+
*
|
|
42
|
+
* Объявленность свойства здесь не проверяется намеренно: незнакомое свойство — опечатка, и о
|
|
43
|
+
* ней говорят вслух отказом раскладки. Считай эта функция такое имя ресурсом без требования,
|
|
44
|
+
* ресурс лёг бы всякому дереву, а опечатку не заметил бы никто.
|
|
45
|
+
*/
|
|
46
|
+
export function requirementOf(file) {
|
|
47
|
+
const parts = file.split('.');
|
|
48
|
+
if (parts.length < 3) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const spoken = parts[parts.length - 2];
|
|
52
|
+
if (!spoken.startsWith(REQUIREMENT_PREFIX)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const trait = spoken.slice(REQUIREMENT_PREFIX.length);
|
|
56
|
+
return trait === '' ? null : trait;
|
|
57
|
+
}
|
|
58
|
+
/** Имя файла без требования: под ним ресурс и ложится в дерево. */
|
|
59
|
+
export function withoutRequirement(file, requirement) {
|
|
60
|
+
if (requirement === null) {
|
|
61
|
+
return file;
|
|
62
|
+
}
|
|
63
|
+
const parts = file.split('.');
|
|
64
|
+
parts.splice(parts.length - 2, 1);
|
|
65
|
+
return parts.join('.');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Отвечает ли дерево на требование ресурса.
|
|
69
|
+
*
|
|
70
|
+
* Ресурс без требования берётся всегда. Требующий — только когда дерево назвало это свойство
|
|
71
|
+
* своим: молчание требованию не отвечает. Класть наугад дороже — пустой компаньон возвращает
|
|
72
|
+
* ровно ту работу, ради которой требования и заведены.
|
|
73
|
+
*/
|
|
74
|
+
export function answersRequirement(requirement, has) {
|
|
75
|
+
return requirement === null || has.includes(requirement);
|
|
76
|
+
}
|
|
77
|
+
/** Названные свойства, которых пакет не объявлял, — опечатки, и о них говорят по именам. */
|
|
78
|
+
export function unknownTraits(named, traits) {
|
|
79
|
+
const declared = new Set(traits.map((trait) => trait.value));
|
|
80
|
+
return named.filter((name) => !declared.has(name));
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=traits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traits.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/traits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AASjC,MAAM,CAAC,MAAM,WAAW,GAAW,aAAa,CAAC;AAEjD,8EAA8E;AAC9E,MAAM,CAAC,MAAM,kBAAkB,GAAW,QAAQ,CAAC;AAEnD,kFAAkF;AAClF,MAAM,UAAU,UAAU,CAAC,SAAiB;IACxC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACD,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC1C,OAAO,EAAE,CAAC;IACd,CAAC;IAED,OAAO,MAAM,CAAC,OAAO,CAAC,GAA6B,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAmB,EAAU,EAAE,CAAC,CAAC;QACpG,KAAK;QACL,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;KAC3B,CAAC,CAAC,CAAC;AACR,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACtC,MAAM,KAAK,GAAsB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAW,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,KAAK,GAAW,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAE9D,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AACvC,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,WAA0B;IACvE,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,KAAK,GAAa,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACxC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAElC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAA0B,EAAE,GAAsB;IACjF,OAAO,WAAW,KAAK,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC7D,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,aAAa,CAAC,KAAwB,EAAE,MAAyB;IAC7E,MAAM,QAAQ,GAAgB,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAE1F,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AACxE,CAAC","sourcesContent":["/**\n * Свойства дерева и требования ресурсов к ним.\n *\n * Часть правил верна только там, где есть хранилище, админка или процедуры: наблюдаемость\n * говорит про строки лога и номер обращения, владеющая сущность захода — про запись, которой\n * заход принадлежит. Дереву, которое публикует библиотеки, они не нужны ни одной статьёй, и\n * положенные ему — не долг, а лишние файлы: заполнить их компаньоны нечем.\n *\n * От оси различия это отличается вопросом. Ось спрашивает «какой из трёх» и выбирает ровно\n * один вид; здесь вопрос «есть ли», и таких вещей у одного дерева сразу несколько: дерево,\n * которое публикует пакеты и держит приложение, одним значением не описывается.\n *\n * Требование стоит в имени файла приставкой `needs-`: `observability.needs-db.md`. Приставка —\n * то, чем требование отличимо от вида: `github` читается перечнем осей, `needs-db` — перечнем\n * свойств, и перепутать их нельзя.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface ITrait {\n /** Значение, которое дерево пишет в настройку и которое стоит в имени файла после `needs-`. */\n readonly value: string;\n /** Что это свойство означает — им человек и выбирает, что писать про своё дерево. */\n readonly title: string;\n}\n\nexport const TRAITS_FILE: string = 'traits.json';\n\n/** Приставка требования в имени файла. Ею требование и отличается от вида. */\nexport const REQUIREMENT_PREFIX: string = 'needs-';\n\n/** Свойства, объявленные пакетом. Нет файла — свойств нет, и требовать нечего. */\nexport function readTraits(assetsDir: string): readonly ITrait[] {\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(join(assetsDir, TRAITS_FILE), 'utf8'));\n } catch {\n return [];\n }\n if (typeof raw !== 'object' || raw === null) {\n return [];\n }\n\n return Object.entries(raw as Record<string, ITrait>).map(([value, trait]: [string, ITrait]): ITrait => ({\n value,\n title: trait.title ?? '',\n }));\n}\n\n/**\n * Свойство, которого ресурс требует именем файла, или `null`, если требования нет.\n *\n * Объявленность свойства здесь не проверяется намеренно: незнакомое свойство — опечатка, и о\n * ней говорят вслух отказом раскладки. Считай эта функция такое имя ресурсом без требования,\n * ресурс лёг бы всякому дереву, а опечатку не заметил бы никто.\n */\nexport function requirementOf(file: string): string | null {\n const parts: readonly string[] = file.split('.');\n if (parts.length < 3) {\n return null;\n }\n const spoken: string = parts[parts.length - 2];\n if (!spoken.startsWith(REQUIREMENT_PREFIX)) {\n return null;\n }\n const trait: string = spoken.slice(REQUIREMENT_PREFIX.length);\n\n return trait === '' ? null : trait;\n}\n\n/** Имя файла без требования: под ним ресурс и ложится в дерево. */\nexport function withoutRequirement(file: string, requirement: string | null): string {\n if (requirement === null) {\n return file;\n }\n const parts: string[] = file.split('.');\n parts.splice(parts.length - 2, 1);\n\n return parts.join('.');\n}\n\n/**\n * Отвечает ли дерево на требование ресурса.\n *\n * Ресурс без требования берётся всегда. Требующий — только когда дерево назвало это свойство\n * своим: молчание требованию не отвечает. Класть наугад дороже — пустой компаньон возвращает\n * ровно ту работу, ради которой требования и заведены.\n */\nexport function answersRequirement(requirement: string | null, has: readonly string[]): boolean {\n return requirement === null || has.includes(requirement);\n}\n\n/** Названные свойства, которых пакет не объявлял, — опечатки, и о них говорят по именам. */\nexport function unknownTraits(named: readonly string[], traits: readonly ITrait[]): readonly string[] {\n const declared: Set<string> = new Set(traits.map((trait: ITrait): string => trait.value));\n\n return named.filter((name: string): boolean => !declared.has(name));\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rt-tools/agent-kit",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Переносимый слой правил для агента: законы, хуки, проверки и агенты, раскладываемые в репозиторий одной командой",
|
|
5
5
|
"author": "RT Team",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"types": "./index.d.ts",
|
|
38
38
|
"default": "./index.js"
|
|
39
39
|
},
|
|
40
|
-
"./package.json": "./package.json"
|
|
40
|
+
"./package.json": "./package.json",
|
|
41
|
+
"./cargo": {
|
|
42
|
+
"types": "./lib/cargo.d.ts",
|
|
43
|
+
"default": "./lib/cargo.js"
|
|
44
|
+
}
|
|
41
45
|
}
|
|
42
46
|
}
|
|
Binary file
|
package/lib/submit.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface IIssueInput {
|
|
2
|
-
/** `владелец/репозиторий` — читается из манифеста пакета, а не зашито в код. */
|
|
3
|
-
readonly repository: string;
|
|
4
|
-
readonly title: string;
|
|
5
|
-
readonly body: string;
|
|
6
|
-
readonly label: string;
|
|
7
|
-
}
|
|
8
|
-
/** Чем заводится запись. Двойник в спеке — того же вида. */
|
|
9
|
-
export type TSubmit = (input: IIssueInput) => string;
|
|
10
|
-
/** Метка, по которой сведение находит предложения среди прочих записей. */
|
|
11
|
-
export declare const FEEDBACK_LABEL: string;
|
|
12
|
-
/**
|
|
13
|
-
* `владелец/репозиторий` из адреса, записанного в манифесте пакета. Форм адреса несколько —
|
|
14
|
-
* `git+https://…`, `git@…:…`, — и все они ведут к одной паре имён.
|
|
15
|
-
*/
|
|
16
|
-
export declare function repositoryOf(url: string): string;
|
|
17
|
-
/**
|
|
18
|
-
* Запись в очереди работ через помощника хостинга.
|
|
19
|
-
*
|
|
20
|
-
* Помощник может быть подменён оболочкой пользователя, поэтому зовётся он именем, а не строкой
|
|
21
|
-
* оболочки: подмена принимает те же доводы, но уходит в чужую учётную запись.
|
|
22
|
-
*/
|
|
23
|
-
export declare const ghIssue: TSubmit;
|
|
24
|
-
//# sourceMappingURL=submit.d.ts.map
|