@rt-tools/agent-kit 0.3.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 +194 -30
- 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 +0 -0
- package/assets/hooks/browser-guard-device-id.sh +2 -1
- package/assets/hooks/browser-guard-no-asking.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +2 -1
- package/assets/hooks/browser-guard-no-other-drivers.sh +2 -1
- package/assets/hooks/browser-guard-require-select.sh +2 -1
- package/assets/hooks/commit-msg.sh +1 -1
- package/assets/hooks/constitution-index.sh +5 -4
- package/assets/hooks/dev-server-guard.sh +8 -6
- package/assets/hooks/docs-guard.sh +223 -37
- package/assets/hooks/git-guard-delivery.sh +86 -29
- package/assets/hooks/git-guard-main.sh +1 -0
- package/assets/hooks/git-guard-push-tests.sh +34 -13
- package/assets/hooks/glossary-load.sh +23 -0
- package/assets/hooks/lint-after-edit.sh +155 -30
- package/assets/hooks/qa-dataid-guard.sh +72 -32
- package/assets/hooks/reuse-first-guard.sh +105 -34
- package/assets/hooks/skill-gate-rearm.sh +1 -0
- package/assets/hooks/skill-gate.sh +75 -15
- package/assets/hooks/skill-loaded.sh +1 -0
- package/assets/hooks/sql-guard.sh +606 -56
- 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 +29 -22
- package/assets/patterns/api-layer-pair.md +40 -30
- package/assets/patterns/browser-verification-measure.md +41 -38
- package/assets/patterns/browser-verification-stand.md +106 -42
- package/assets/patterns/component-structure-new.md +33 -32
- package/assets/patterns/dependencies-upgrade.md +65 -0
- package/assets/patterns/doc-style-sweep.md +65 -28
- package/assets/patterns/doc-style-write.md +36 -33
- 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 +42 -25
- package/assets/patterns/git-workflow-migration.md +61 -31
- package/assets/patterns/git-workflow-restart.md +20 -20
- package/assets/patterns/lib-layers-move.md +50 -32
- package/assets/patterns/lib-layers-new.md +41 -29
- package/assets/patterns/ownership-scope-resolve.md +69 -0
- package/assets/patterns/permissions-procedure.md +35 -33
- package/assets/patterns/platform-access-di.md +39 -25
- package/assets/patterns/pricing-quote.md +71 -0
- package/assets/patterns/reuse-first-extend.md +22 -22
- package/assets/patterns/seo-page.md +52 -40
- package/assets/patterns/seo-verify.md +48 -29
- package/assets/patterns/shared-code-new.md +37 -31
- package/assets/patterns/spec-driven-domain.md +44 -37
- package/assets/patterns/spec-driven-rule.md +55 -40
- package/assets/patterns/styling-bem-component.md +43 -32
- package/assets/patterns/styling-bem-layout.md +30 -24
- 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 +53 -51
- package/assets/patterns/testing-unit.md +70 -46
- package/assets/patterns/translations-key.md +32 -19
- package/assets/patterns/ts-procedure.md +24 -25
- package/assets/rules/angular-patterns.md +46 -27
- package/assets/rules/api-layer.md +46 -28
- package/assets/rules/browser-verification.md +66 -48
- package/assets/rules/component-structure.md +43 -27
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +81 -39
- 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 +56 -30
- 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 +43 -25
- package/assets/rules/platform-access.md +57 -29
- package/assets/rules/pricing.md +64 -0
- package/assets/rules/reuse-first.md +57 -43
- package/assets/rules/seo.md +51 -30
- package/assets/rules/shared-code.md +51 -26
- package/assets/rules/spec-driven.md +96 -50
- package/assets/rules/styling-bem.md +54 -39
- package/assets/rules/task-flow.md +110 -0
- package/assets/rules/testing.md +78 -47
- package/assets/rules/translations.md +48 -31
- package/assets/rules/typescript-conventions.md +57 -27
- package/assets/skills/agent-kit.md +81 -0
- package/assets/skills/write-a-skill.md +108 -0
- package/assets/templates/gate-map.sh +23 -15
- package/assets/templates/implementation.md +14 -8
- package/assets/templates/pattern.md +1 -1
- package/assets/templates/project.sh +32 -19
- package/assets/templates/rule.md +1 -1
- 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 +8 -3
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +13 -3
- 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 +202 -14
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +5 -1
- package/lib/companion.d.ts.map +1 -1
- package/lib/companion.js +29 -2
- package/lib/companion.js.map +1 -1
- package/lib/config.d.ts +26 -9
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +41 -15
- 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/sync.d.ts +26 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +59 -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/assets/patterns/git-workflow-commit.md +0 -175
- package/assets/rules/git-workflow.md +0 -106
- package/rt-tools-agent-kit-0.3.0.tgz +0 -0
package/lib/freshness.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Свежесть собранного пакета относительно его исходников.
|
|
3
|
+
*
|
|
4
|
+
* Строка запуска читает собранное, а правка ресурса идёт в исходники. Между ними стоит сборка,
|
|
5
|
+
* и пока её не прогнали, `sync` отвечает «всё уже разложено» — притом что в дереве лежит
|
|
6
|
+
* прежняя редакция ресурса. Неправда эта дороже отказа: её замечают, когда правленое правило не
|
|
7
|
+
* действует, и ищут причину в раскладке, а не в том, что её не собрали.
|
|
8
|
+
*
|
|
9
|
+
* Исходники ищутся только там, где они есть: у того, кто пакет разрабатывает. У потребителя
|
|
10
|
+
* рядом с установленным пакетом нет ни рабочего пространства, ни каталога проектов, и проверка
|
|
11
|
+
* молчит — сверять не с чем и нечего.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
15
|
+
/** Признаки корня рабочего пространства: по ним ищется дерево, в котором пакет разрабатывают. */
|
|
16
|
+
const WORKSPACE_MARKS = ['nx.json', 'pnpm-workspace.yaml', 'lerna.json'];
|
|
17
|
+
/** Каталоги, в которых рабочее пространство держит проекты. */
|
|
18
|
+
const PROJECT_DIRS = ['projects', 'packages', 'libs', 'apps'];
|
|
19
|
+
/** Самое позднее время правки в поддереве и файл, которому оно принадлежит. */
|
|
20
|
+
function newestIn(dir) {
|
|
21
|
+
let found = null;
|
|
22
|
+
const walk = (current) => {
|
|
23
|
+
let entries;
|
|
24
|
+
try {
|
|
25
|
+
entries = readdirSync(current);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const name of entries) {
|
|
31
|
+
const path = join(current, name);
|
|
32
|
+
let stats;
|
|
33
|
+
try {
|
|
34
|
+
stats = statSync(path);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (stats.isDirectory()) {
|
|
40
|
+
walk(path);
|
|
41
|
+
}
|
|
42
|
+
else if (!found || stats.mtimeMs > found.at) {
|
|
43
|
+
found = { at: stats.mtimeMs, path };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
walk(dir);
|
|
48
|
+
return found;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Корень рабочего пространства над этим каталогом.
|
|
52
|
+
*
|
|
53
|
+
* Подъём заканчивается на корне файловой системы, а не на отсчитанном числе шагов: глубина, на
|
|
54
|
+
* которой лежит установленный пакет, у каждого дерева своя, и отсчитанный предел молчал бы ровно
|
|
55
|
+
* там, где вложенность оказалась глубже придуманной.
|
|
56
|
+
*/
|
|
57
|
+
function workspaceOf(start) {
|
|
58
|
+
let dir = start;
|
|
59
|
+
for (;;) {
|
|
60
|
+
if (WORKSPACE_MARKS.some((mark) => existsSync(join(dir, mark)))) {
|
|
61
|
+
return dir;
|
|
62
|
+
}
|
|
63
|
+
const parent = dirname(dir);
|
|
64
|
+
if (parent === dir) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
dir = parent;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Каталог исходников пакета с этим именем внутри рабочего пространства. */
|
|
71
|
+
function sourceOf(workspace, name) {
|
|
72
|
+
for (const group of PROJECT_DIRS) {
|
|
73
|
+
let entries;
|
|
74
|
+
try {
|
|
75
|
+
entries = readdirSync(join(workspace, group));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const candidate = join(workspace, group, entry);
|
|
82
|
+
try {
|
|
83
|
+
if (JSON.parse(readFileSync(join(candidate, 'package.json'), 'utf8')).name === name) {
|
|
84
|
+
return candidate;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Правлены ли ресурсы позже, чем собран пакет, из которого идёт раскладка.
|
|
96
|
+
*
|
|
97
|
+
* `null` значит «сверять не с чем или сверка сошлась»: у потребителя исходников нет вовсе, а у
|
|
98
|
+
* того, кто запускает пакет прямо из исходников, собранное и исходное — один и тот же каталог.
|
|
99
|
+
*/
|
|
100
|
+
export function staleBuild(packageRoot, name) {
|
|
101
|
+
const workspace = workspaceOf(packageRoot);
|
|
102
|
+
if (workspace === null) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const source = sourceOf(workspace, name);
|
|
106
|
+
if (source === null || source === packageRoot) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const built = newestIn(join(packageRoot, 'assets'));
|
|
110
|
+
const written = newestIn(join(source, 'assets'));
|
|
111
|
+
if (!built || !written || written.at <= built.at) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return { source: join(source, 'assets'), newest: written.path };
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=freshness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/freshness.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Свежесть собранного пакета относительно его исходников.\n *\n * Строка запуска читает собранное, а правка ресурса идёт в исходники. Между ними стоит сборка,\n * и пока её не прогнали, `sync` отвечает «всё уже разложено» — притом что в дереве лежит\n * прежняя редакция ресурса. Неправда эта дороже отказа: её замечают, когда правленое правило не\n * действует, и ищут причину в раскладке, а не в том, что её не собрали.\n *\n * Исходники ищутся только там, где они есть: у того, кто пакет разрабатывает. У потребителя\n * рядом с установленным пакетом нет ни рабочего пространства, ни каталога проектов, и проверка\n * молчит — сверять не с чем и нечего.\n */\nimport { existsSync, readdirSync, readFileSync, statSync, Stats } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\n/** Признаки корня рабочего пространства: по ним ищется дерево, в котором пакет разрабатывают. */\nconst WORKSPACE_MARKS: readonly string[] = ['nx.json', 'pnpm-workspace.yaml', 'lerna.json'];\n\n/** Каталоги, в которых рабочее пространство держит проекты. */\nconst PROJECT_DIRS: readonly string[] = ['projects', 'packages', 'libs', 'apps'];\n\nexport interface IStaleBuild {\n /** Каталог ресурсов в исходниках. */\n readonly source: string;\n /** Файл ресурса, правленный позже сборки, — по нему видно, чего именно не хватает в дереве. */\n readonly newest: string;\n}\n\ninterface INewest {\n readonly at: number;\n readonly path: string;\n}\n\n/** Самое позднее время правки в поддереве и файл, которому оно принадлежит. */\nfunction newestIn(dir: string): INewest | null {\n let found: INewest | null = null;\n\n const walk: (current: string) => void = (current: string): void => {\n let entries: readonly string[];\n try {\n entries = readdirSync(current);\n } catch {\n return;\n }\n for (const name of entries) {\n const path: string = join(current, name);\n let stats: Stats;\n try {\n stats = statSync(path);\n } catch {\n continue;\n }\n if (stats.isDirectory()) {\n walk(path);\n } else if (!found || stats.mtimeMs > found.at) {\n found = { at: stats.mtimeMs, path };\n }\n }\n };\n walk(dir);\n\n return found;\n}\n\n/**\n * Корень рабочего пространства над этим каталогом.\n *\n * Подъём заканчивается на корне файловой системы, а не на отсчитанном числе шагов: глубина, на\n * которой лежит установленный пакет, у каждого дерева своя, и отсчитанный предел молчал бы ровно\n * там, где вложенность оказалась глубже придуманной.\n */\nfunction workspaceOf(start: string): string | null {\n let dir: string = start;\n for (;;) {\n if (WORKSPACE_MARKS.some((mark: string): boolean => existsSync(join(dir, mark)))) {\n return dir;\n }\n const parent: string = dirname(dir);\n if (parent === dir) {\n return null;\n }\n dir = parent;\n }\n}\n\n/** Каталог исходников пакета с этим именем внутри рабочего пространства. */\nfunction sourceOf(workspace: string, name: string): string | null {\n for (const group of PROJECT_DIRS) {\n let entries: readonly string[];\n try {\n entries = readdirSync(join(workspace, group));\n } catch {\n continue;\n }\n for (const entry of entries) {\n const candidate: string = join(workspace, group, entry);\n try {\n if (JSON.parse(readFileSync(join(candidate, 'package.json'), 'utf8')).name === name) {\n return candidate;\n }\n } catch {\n continue;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Правлены ли ресурсы позже, чем собран пакет, из которого идёт раскладка.\n *\n * `null` значит «сверять не с чем или сверка сошлась»: у потребителя исходников нет вовсе, а у\n * того, кто запускает пакет прямо из исходников, собранное и исходное — один и тот же каталог.\n */\nexport function staleBuild(packageRoot: string, name: string): IStaleBuild | null {\n const workspace: string | null = workspaceOf(packageRoot);\n if (workspace === null) {\n return null;\n }\n const source: string | null = sourceOf(workspace, name);\n if (source === null || source === packageRoot) {\n return null;\n }\n\n const built: INewest | null = newestIn(join(packageRoot, 'assets'));\n const written: INewest | null = newestIn(join(source, 'assets'));\n if (!built || !written || written.at <= built.at) {\n return null;\n }\n\n return { source: join(source, 'assets'), newest: written.path };\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Настройка агента, в которой живёт карта. Путь от корня дерева. */
|
|
2
|
+
export declare const SETTINGS_PATH: string;
|
|
3
|
+
export interface IHookBinding {
|
|
4
|
+
/** Событие агента: `PreToolUse`, `PostToolUse`, `SessionStart`. */
|
|
5
|
+
readonly event: string;
|
|
6
|
+
/** Образец, по которому событие достаётся этому гарду. */
|
|
7
|
+
readonly matcher: string;
|
|
8
|
+
/** Путь разложенного гарда от корня дерева. */
|
|
9
|
+
readonly path: string;
|
|
10
|
+
}
|
|
11
|
+
/** Что гард говорит о себе. Молчит — он не подключается к агенту вовсе. */
|
|
12
|
+
export declare function bindingOf(text: string, path: string): IHookBinding | null;
|
|
13
|
+
/**
|
|
14
|
+
* Раздел `hooks` для настройки агента: событие → образец → команда.
|
|
15
|
+
*
|
|
16
|
+
* Гарды с одинаковым образцом внутри события собираются в одну запись — так же, как это пишут
|
|
17
|
+
* руками, и так же, как читает агент.
|
|
18
|
+
*/
|
|
19
|
+
export declare function hooksSection(bindings: readonly IHookBinding[]): Record<string, unknown>;
|
|
20
|
+
/** Пути гардов, названные в настройке агента этого дерева. Нет файла — ни одного. */
|
|
21
|
+
export declare function boundInSettings(root: string): readonly string[];
|
|
22
|
+
/** Разложенные гарды, которых в настройке агента нет: они лежат, но их никто не позовёт. */
|
|
23
|
+
export declare function unboundHooks(bindings: readonly IHookBinding[], root: string): readonly IHookBinding[];
|
|
24
|
+
//# sourceMappingURL=hooks-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-map.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/hooks-map.ts"],"names":[],"mappings":"AAmBA,qEAAqE;AACrE,eAAO,MAAM,aAAa,EAAE,MAAgC,CAAC;AAI7D,MAAM,WAAW,YAAY;IACzB,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAIzE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBvF;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAkB/D;AAED,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,YAAY,EAAE,CAIrG"}
|
package/lib/hooks-map.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Карта хуков: чем разложенные гарды подключаются к агенту.
|
|
3
|
+
*
|
|
4
|
+
* Раскладка кладёт файлы, а зовёт их запись в настройке агента — чужом JSON, которым пакет не
|
|
5
|
+
* владеет. Пока о ней молчали, свежее дерево получало два десятка гардов, ни один из которых не
|
|
6
|
+
* срабатывал, и узнавало об этом, когда что-нибудь проходило мимо.
|
|
7
|
+
*
|
|
8
|
+
* Пакет не пишет в чужую настройку сам: файл держит проект, в нём лежит и то, о чём пакет не
|
|
9
|
+
* знает вовсе, а слияние чужого JSON молча теряет несовпавшее. Вместо этого он собирает готовый
|
|
10
|
+
* кусок и проверяет, что он вставлен. Это дешевле и честнее: правку в свою настройку делает тот,
|
|
11
|
+
* кто за неё отвечает.
|
|
12
|
+
*
|
|
13
|
+
* Событие и образец вызова гард несёт сам — строкой `# rt-hook: <событие> <образец>` во второй
|
|
14
|
+
* строке файла. Карта, выписанная отдельным списком, разошлась бы с набором гардов на первом же
|
|
15
|
+
* добавленном, и заметить это было бы нечем: гард просто не звался бы.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
/** Настройка агента, в которой живёт карта. Путь от корня дерева. */
|
|
20
|
+
export const SETTINGS_PATH = '.claude/settings.json';
|
|
21
|
+
const DECLARATION = /^#\s*rt-hook:\s*(\S+)\s+(\S.*)$/m;
|
|
22
|
+
/** Что гард говорит о себе. Молчит — он не подключается к агенту вовсе. */
|
|
23
|
+
export function bindingOf(text, path) {
|
|
24
|
+
const found = text.match(DECLARATION);
|
|
25
|
+
return found ? { event: found[1], matcher: found[2].trim(), path } : null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Раздел `hooks` для настройки агента: событие → образец → команда.
|
|
29
|
+
*
|
|
30
|
+
* Гарды с одинаковым образцом внутри события собираются в одну запись — так же, как это пишут
|
|
31
|
+
* руками, и так же, как читает агент.
|
|
32
|
+
*/
|
|
33
|
+
export function hooksSection(bindings) {
|
|
34
|
+
const events = new Map();
|
|
35
|
+
for (const binding of bindings) {
|
|
36
|
+
const byMatcher = events.get(binding.event) ?? new Map();
|
|
37
|
+
byMatcher.set(binding.matcher, [...(byMatcher.get(binding.matcher) ?? []), binding.path]);
|
|
38
|
+
events.set(binding.event, byMatcher);
|
|
39
|
+
}
|
|
40
|
+
const section = {};
|
|
41
|
+
for (const [event, byMatcher] of [...events].sort()) {
|
|
42
|
+
section[event] = [...byMatcher].sort().map(([matcher, paths]) => ({
|
|
43
|
+
matcher,
|
|
44
|
+
hooks: paths.map((path) => ({ type: 'command', command: `$CLAUDE_PROJECT_DIR/${path}` })),
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
return section;
|
|
48
|
+
}
|
|
49
|
+
/** Пути гардов, названные в настройке агента этого дерева. Нет файла — ни одного. */
|
|
50
|
+
export function boundInSettings(root) {
|
|
51
|
+
const path = join(root, SETTINGS_PATH);
|
|
52
|
+
if (!existsSync(path)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
let text;
|
|
56
|
+
try {
|
|
57
|
+
text = readFileSync(path, 'utf8');
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
// Настройка читается текстом, а не разбором: у неё нет объявленной формы, и дерево вправе
|
|
63
|
+
// держать её так, как ему удобно, — вплоть до комментариев, которых JSON не разбирает. Ищется
|
|
64
|
+
// одно: назван ли путь гарда хоть где-нибудь в ней.
|
|
65
|
+
return [...text.matchAll(/[\w./$-]*\.claude\/hooks\/[\w.-]+\.sh/g)].map((found) => found[0].replace(/^.*?(\.claude\/)/, '$1'));
|
|
66
|
+
}
|
|
67
|
+
/** Разложенные гарды, которых в настройке агента нет: они лежат, но их никто не позовёт. */
|
|
68
|
+
export function unboundHooks(bindings, root) {
|
|
69
|
+
const bound = new Set(boundInSettings(root));
|
|
70
|
+
return bindings.filter((binding) => !bound.has(binding.path));
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=hooks-map.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-map.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/hooks-map.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Карта хуков: чем разложенные гарды подключаются к агенту.\n *\n * Раскладка кладёт файлы, а зовёт их запись в настройке агента — чужом JSON, которым пакет не\n * владеет. Пока о ней молчали, свежее дерево получало два десятка гардов, ни один из которых не\n * срабатывал, и узнавало об этом, когда что-нибудь проходило мимо.\n *\n * Пакет не пишет в чужую настройку сам: файл держит проект, в нём лежит и то, о чём пакет не\n * знает вовсе, а слияние чужого JSON молча теряет несовпавшее. Вместо этого он собирает готовый\n * кусок и проверяет, что он вставлен. Это дешевле и честнее: правку в свою настройку делает тот,\n * кто за неё отвечает.\n *\n * Событие и образец вызова гард несёт сам — строкой `# rt-hook: <событие> <образец>` во второй\n * строке файла. Карта, выписанная отдельным списком, разошлась бы с набором гардов на первом же\n * добавленном, и заметить это было бы нечем: гард просто не звался бы.\n */\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Настройка агента, в которой живёт карта. Путь от корня дерева. */\nexport const SETTINGS_PATH: string = '.claude/settings.json';\n\nconst DECLARATION: RegExp = /^#\\s*rt-hook:\\s*(\\S+)\\s+(\\S.*)$/m;\n\nexport interface IHookBinding {\n /** Событие агента: `PreToolUse`, `PostToolUse`, `SessionStart`. */\n readonly event: string;\n /** Образец, по которому событие достаётся этому гарду. */\n readonly matcher: string;\n /** Путь разложенного гарда от корня дерева. */\n readonly path: string;\n}\n\n/** Что гард говорит о себе. Молчит — он не подключается к агенту вовсе. */\nexport function bindingOf(text: string, path: string): IHookBinding | null {\n const found: RegExpMatchArray | null = text.match(DECLARATION);\n\n return found ? { event: found[1], matcher: found[2].trim(), path } : null;\n}\n\n/**\n * Раздел `hooks` для настройки агента: событие → образец → команда.\n *\n * Гарды с одинаковым образцом внутри события собираются в одну запись — так же, как это пишут\n * руками, и так же, как читает агент.\n */\nexport function hooksSection(bindings: readonly IHookBinding[]): Record<string, unknown> {\n const events: Map<string, Map<string, string[]>> = new Map();\n\n for (const binding of bindings) {\n const byMatcher: Map<string, string[]> = events.get(binding.event) ?? new Map();\n byMatcher.set(binding.matcher, [...(byMatcher.get(binding.matcher) ?? []), binding.path]);\n events.set(binding.event, byMatcher);\n }\n\n const section: Record<string, unknown> = {};\n for (const [event, byMatcher] of [...events].sort()) {\n section[event] = [...byMatcher].sort().map(([matcher, paths]: [string, readonly string[]]): unknown => ({\n matcher,\n hooks: paths.map((path: string): unknown => ({ type: 'command', command: `$CLAUDE_PROJECT_DIR/${path}` })),\n }));\n }\n\n return section;\n}\n\n/** Пути гардов, названные в настройке агента этого дерева. Нет файла — ни одного. */\nexport function boundInSettings(root: string): readonly string[] {\n const path: string = join(root, SETTINGS_PATH);\n if (!existsSync(path)) {\n return [];\n }\n let text: string;\n try {\n text = readFileSync(path, 'utf8');\n } catch {\n return [];\n }\n\n // Настройка читается текстом, а не разбором: у неё нет объявленной формы, и дерево вправе\n // держать её так, как ему удобно, — вплоть до комментариев, которых JSON не разбирает. Ищется\n // одно: назван ли путь гарда хоть где-нибудь в ней.\n return [...text.matchAll(/[\\w./$-]*\\.claude\\/hooks\\/[\\w.-]+\\.sh/g)].map((found: RegExpMatchArray): string =>\n found[0].replace(/^.*?(\\.claude\\/)/, '$1')\n );\n}\n\n/** Разложенные гарды, которых в настройке агента нет: они лежат, но их никто не позовёт. */\nexport function unboundHooks(bindings: readonly IHookBinding[], root: string): readonly IHookBinding[] {\n const bound: ReadonlySet<string> = new Set(boundInSettings(root));\n\n return bindings.filter((binding: IHookBinding): boolean => !bound.has(binding.path));\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Целостность самих ресурсов пакета: у правила есть закон, у паттерна — правило.
|
|
3
|
+
*
|
|
4
|
+
* Проверка нужна не проекту, а пакету. Закон переименовали, а `law:` у правила осталось
|
|
5
|
+
* прежним — раскладка от этого не падает: она кладёт файлы, а не читает их шапки. Промах уезжает
|
|
6
|
+
* к проектам, и находит его тот, кто пошёл за законом по ссылке из правила и не нашёл его.
|
|
7
|
+
*
|
|
8
|
+
* Разбирается только вступление между `---`: остальное в файле — проза, и `law:` посреди неё
|
|
9
|
+
* означает пример, а не объявление.
|
|
10
|
+
*/
|
|
11
|
+
import { IEntryOfCatalog } from './catalog.js';
|
|
12
|
+
/** Что объявила шапка ресурса. Пустое поле и отсутствующее здесь одно и то же. */
|
|
13
|
+
export interface IFrontMatter {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly kind: string;
|
|
16
|
+
/** Закон, под которым стоит правило. */
|
|
17
|
+
readonly law: string;
|
|
18
|
+
/** Правило, при котором стоит паттерн. */
|
|
19
|
+
readonly rule: string;
|
|
20
|
+
}
|
|
21
|
+
/** Вступление между `---` в начале файла; его нет — вернётся пустая шапка. */
|
|
22
|
+
export declare function frontMatterOf(text: string): IFrontMatter;
|
|
23
|
+
/** Расхождение в ресурсах пакета: кто ссылается, на что и чего не нашлось. */
|
|
24
|
+
export interface IBrokenLink {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly field: 'law' | 'rule';
|
|
27
|
+
readonly wanted: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Ссылки шапок, которым в каталоге ничего не отвечает.
|
|
31
|
+
*
|
|
32
|
+
* Закон ищется по короткому имени: слой в ссылке не отражается, и переезд закона между слоями
|
|
33
|
+
* шапок правил не переписывает — так же, как этого не делают ни таблицы законов, ни спеки.
|
|
34
|
+
*/
|
|
35
|
+
export declare function brokenLinks(catalog: readonly IEntryOfCatalog[]): readonly IBrokenLink[];
|
|
36
|
+
//# sourceMappingURL=integrity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integrity.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/integrity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,kFAAkF;AAClF,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAID,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAiBxD;AAED,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,SAAS,WAAW,EAAE,CAoBvF"}
|
package/lib/integrity.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const FIELD = /^([a-z]+):\s*(\S.*?)\s*$/;
|
|
2
|
+
/** Вступление между `---` в начале файла; его нет — вернётся пустая шапка. */
|
|
3
|
+
export function frontMatterOf(text) {
|
|
4
|
+
const lines = text.split('\n');
|
|
5
|
+
const found = {};
|
|
6
|
+
if (lines[0]?.trim() === '---') {
|
|
7
|
+
for (const line of lines.slice(1)) {
|
|
8
|
+
if (line.trim() === '---') {
|
|
9
|
+
break;
|
|
10
|
+
}
|
|
11
|
+
const match = line.match(FIELD);
|
|
12
|
+
if (match) {
|
|
13
|
+
found[match[1]] = match[2];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { name: found['name'] ?? '', kind: found['kind'] ?? '', law: found['law'] ?? '', rule: found['rule'] ?? '' };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Ссылки шапок, которым в каталоге ничего не отвечает.
|
|
21
|
+
*
|
|
22
|
+
* Закон ищется по короткому имени: слой в ссылке не отражается, и переезд закона между слоями
|
|
23
|
+
* шапок правил не переписывает — так же, как этого не делают ни таблицы законов, ни спеки.
|
|
24
|
+
*/
|
|
25
|
+
export function brokenLinks(catalog) {
|
|
26
|
+
const shortOf = (entry) => entry.name.split('/').pop() ?? entry.name;
|
|
27
|
+
const laws = new Set(catalog.filter((entry) => entry.kind === 'laws').map(shortOf));
|
|
28
|
+
const rules = new Set(catalog.filter((entry) => entry.kind === 'rules').map(shortOf));
|
|
29
|
+
const broken = [];
|
|
30
|
+
for (const entry of catalog) {
|
|
31
|
+
if (entry.kind !== 'rules' && entry.kind !== 'patterns') {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const head = frontMatterOf(entry.text);
|
|
35
|
+
if (entry.kind === 'rules' && !laws.has(head.law)) {
|
|
36
|
+
broken.push({ id: entry.id, field: 'law', wanted: head.law });
|
|
37
|
+
}
|
|
38
|
+
if (entry.kind === 'patterns' && !rules.has(head.rule)) {
|
|
39
|
+
broken.push({ id: entry.id, field: 'rule', wanted: head.rule });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return broken;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=integrity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integrity.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/integrity.ts"],"names":[],"mappings":"AAsBA,MAAM,KAAK,GAAW,0BAA0B,CAAC;AAEjD,8EAA8E;AAC9E,MAAM,UAAU,aAAa,CAAC,IAAY;IACtC,MAAM,KAAK,GAAsB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,KAAK,GAA2B,EAAE,CAAC;IAEzC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,KAAK,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAChC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,EAAE,CAAC;gBACxB,MAAM;YACV,CAAC;YACD,MAAM,KAAK,GAA4B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACzD,IAAI,KAAK,EAAE,CAAC;gBACR,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YAC/B,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;AACxH,CAAC;AASD;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAmC;IAC3D,MAAM,OAAO,GAAuC,CAAC,KAAsB,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC;IAClI,MAAM,IAAI,GAAwB,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IACnI,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAErI,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACtD,SAAS;QACb,CAAC;QACD,MAAM,IAAI,GAAiB,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACpE,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["/**\n * Целостность самих ресурсов пакета: у правила есть закон, у паттерна — правило.\n *\n * Проверка нужна не проекту, а пакету. Закон переименовали, а `law:` у правила осталось\n * прежним — раскладка от этого не падает: она кладёт файлы, а не читает их шапки. Промах уезжает\n * к проектам, и находит его тот, кто пошёл за законом по ссылке из правила и не нашёл его.\n *\n * Разбирается только вступление между `---`: остальное в файле — проза, и `law:` посреди неё\n * означает пример, а не объявление.\n */\nimport { IEntryOfCatalog } from './catalog.js';\n\n/** Что объявила шапка ресурса. Пустое поле и отсутствующее здесь одно и то же. */\nexport interface IFrontMatter {\n readonly name: string;\n readonly kind: string;\n /** Закон, под которым стоит правило. */\n readonly law: string;\n /** Правило, при котором стоит паттерн. */\n readonly rule: string;\n}\n\nconst FIELD: RegExp = /^([a-z]+):\\s*(\\S.*?)\\s*$/;\n\n/** Вступление между `---` в начале файла; его нет — вернётся пустая шапка. */\nexport function frontMatterOf(text: string): IFrontMatter {\n const lines: readonly string[] = text.split('\\n');\n const found: Record<string, string> = {};\n\n if (lines[0]?.trim() === '---') {\n for (const line of lines.slice(1)) {\n if (line.trim() === '---') {\n break;\n }\n const match: RegExpMatchArray | null = line.match(FIELD);\n if (match) {\n found[match[1]] = match[2];\n }\n }\n }\n\n return { name: found['name'] ?? '', kind: found['kind'] ?? '', law: found['law'] ?? '', rule: found['rule'] ?? '' };\n}\n\n/** Расхождение в ресурсах пакета: кто ссылается, на что и чего не нашлось. */\nexport interface IBrokenLink {\n readonly id: string;\n readonly field: 'law' | 'rule';\n readonly wanted: string;\n}\n\n/**\n * Ссылки шапок, которым в каталоге ничего не отвечает.\n *\n * Закон ищется по короткому имени: слой в ссылке не отражается, и переезд закона между слоями\n * шапок правил не переписывает — так же, как этого не делают ни таблицы законов, ни спеки.\n */\nexport function brokenLinks(catalog: readonly IEntryOfCatalog[]): readonly IBrokenLink[] {\n const shortOf: (entry: IEntryOfCatalog) => string = (entry: IEntryOfCatalog): string => entry.name.split('/').pop() ?? entry.name;\n const laws: ReadonlySet<string> = new Set(catalog.filter((entry: IEntryOfCatalog): boolean => entry.kind === 'laws').map(shortOf));\n const rules: ReadonlySet<string> = new Set(catalog.filter((entry: IEntryOfCatalog): boolean => entry.kind === 'rules').map(shortOf));\n\n const broken: IBrokenLink[] = [];\n for (const entry of catalog) {\n if (entry.kind !== 'rules' && entry.kind !== 'patterns') {\n continue;\n }\n const head: IFrontMatter = frontMatterOf(entry.text);\n if (entry.kind === 'rules' && !laws.has(head.law)) {\n broken.push({ id: entry.id, field: 'law', wanted: head.law });\n }\n if (entry.kind === 'patterns' && !rules.has(head.rule)) {\n broken.push({ id: entry.id, field: 'rule', wanted: head.rule });\n }\n }\n\n return broken;\n}\n"]}
|
package/lib/picker.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export interface IPickerState {
|
|
|
16
16
|
readonly choices: readonly IChoice[];
|
|
17
17
|
readonly cursor: number;
|
|
18
18
|
readonly chosen: ReadonlySet<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Выбирается ровно один. Вид ресурса галочками не набирается: репозиторий лежит либо в
|
|
21
|
+
* одном месте, либо в другом, и «оба» здесь означало бы два правила поставки в одном дереве.
|
|
22
|
+
*/
|
|
23
|
+
readonly single: boolean;
|
|
19
24
|
/** Выбор окончен, `chosen` — ответ. */
|
|
20
25
|
readonly done: boolean;
|
|
21
26
|
/** Выбор брошен: ответа нет, и подставлять вместо него умолчание нельзя. */
|
|
@@ -23,6 +28,11 @@ export interface IPickerState {
|
|
|
23
28
|
}
|
|
24
29
|
/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */
|
|
25
30
|
export declare function pickerOf(choices: readonly IChoice[]): IPickerState;
|
|
31
|
+
/**
|
|
32
|
+
* Выбор одного из. Отмечено то, что под курсором, и переезд курсора переносит отметку: так
|
|
33
|
+
* ответ есть в любой момент, и enter отвечает без единого нажатия пробела.
|
|
34
|
+
*/
|
|
35
|
+
export declare function singlePickerOf(choices: readonly IChoice[]): IPickerState;
|
|
26
36
|
export declare const KEY_UP: string;
|
|
27
37
|
export declare const KEY_DOWN: string;
|
|
28
38
|
export declare const KEY_ESCAPE: string;
|
|
@@ -42,6 +52,6 @@ export declare function keysOf(chunk: string): readonly string[];
|
|
|
42
52
|
export declare function pressKey(state: IPickerState, key: string): IPickerState;
|
|
43
53
|
/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */
|
|
44
54
|
export declare const pressChunk: (state: IPickerState, chunk: string) => IPickerState;
|
|
45
|
-
/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком в скобках. */
|
|
55
|
+
/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком или точкой в скобках. */
|
|
46
56
|
export declare function renderPicker(state: IPickerState, question: string): readonly string[];
|
|
47
57
|
//# sourceMappingURL=picker.d.ts.map
|
package/lib/picker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,OAAO;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAED,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,OAAO;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAED,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,YAAY,CASlE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,YAAY,CASxE;AAUD,eAAO,MAAM,MAAM,EAAE,MAAsB,CAAC;AAC5C,eAAO,MAAM,QAAQ,EAAE,MAAsB,CAAC;AAC9C,eAAO,MAAM,UAAU,EAAE,MAAe,CAAC;AACzC,eAAO,MAAM,UAAU,EAAE,MAA+B,CAAC;AA8CzD;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAcvD;AAED,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAwBvE;AAED,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,KAAK,YACxB,CAAC;AAK1C,kGAAkG;AAClG,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAiBrF"}
|
package/lib/picker.js
CHANGED
|
@@ -7,7 +7,28 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */
|
|
9
9
|
export function pickerOf(choices) {
|
|
10
|
-
return {
|
|
10
|
+
return {
|
|
11
|
+
choices,
|
|
12
|
+
cursor: 0,
|
|
13
|
+
chosen: new Set(choices.map((choice) => choice.id)),
|
|
14
|
+
single: false,
|
|
15
|
+
done: false,
|
|
16
|
+
cancelled: false,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Выбор одного из. Отмечено то, что под курсором, и переезд курсора переносит отметку: так
|
|
21
|
+
* ответ есть в любой момент, и enter отвечает без единого нажатия пробела.
|
|
22
|
+
*/
|
|
23
|
+
export function singlePickerOf(choices) {
|
|
24
|
+
return {
|
|
25
|
+
choices,
|
|
26
|
+
cursor: 0,
|
|
27
|
+
chosen: new Set(choices.slice(0, 1).map((choice) => choice.id)),
|
|
28
|
+
single: true,
|
|
29
|
+
done: false,
|
|
30
|
+
cancelled: false,
|
|
31
|
+
};
|
|
11
32
|
}
|
|
12
33
|
/**
|
|
13
34
|
* Клавиши сырого потока. Записаны кодами, а не знаками: управляющий знак посреди исходника
|
|
@@ -27,13 +48,23 @@ const DONE = ['\r', '\n'];
|
|
|
27
48
|
const CANCEL = [KEY_CANCEL, KEY_ESCAPE];
|
|
28
49
|
function moved(state, step) {
|
|
29
50
|
const count = state.choices.length;
|
|
30
|
-
|
|
51
|
+
if (!count) {
|
|
52
|
+
return state;
|
|
53
|
+
}
|
|
54
|
+
const cursor = (state.cursor + step + count) % count;
|
|
55
|
+
const chosen = state.single ? new Set([state.choices[cursor].id]) : state.chosen;
|
|
56
|
+
return { ...state, cursor, chosen };
|
|
31
57
|
}
|
|
32
58
|
function toggled(state) {
|
|
33
59
|
const choice = state.choices[state.cursor];
|
|
34
60
|
if (!choice) {
|
|
35
61
|
return state;
|
|
36
62
|
}
|
|
63
|
+
// При выборе одного пробел ставит отметку, но не снимает: отметить нечего — ответа не
|
|
64
|
+
// осталось бы вовсе, а брошенный выбор здесь говорится отдельной клавишей.
|
|
65
|
+
if (state.single) {
|
|
66
|
+
return { ...state, chosen: new Set([choice.id]) };
|
|
67
|
+
}
|
|
37
68
|
const chosen = new Set(state.chosen);
|
|
38
69
|
if (!chosen.delete(choice.id)) {
|
|
39
70
|
chosen.add(choice.id);
|
|
@@ -41,6 +72,9 @@ function toggled(state) {
|
|
|
41
72
|
return { ...state, chosen };
|
|
42
73
|
}
|
|
43
74
|
function switched(state) {
|
|
75
|
+
if (state.single) {
|
|
76
|
+
return state;
|
|
77
|
+
}
|
|
44
78
|
const all = state.chosen.size === state.choices.length;
|
|
45
79
|
return { ...state, chosen: all ? new Set() : new Set(state.choices.map((choice) => choice.id)) };
|
|
46
80
|
}
|
|
@@ -96,16 +130,20 @@ export function pressKey(state, key) {
|
|
|
96
130
|
/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */
|
|
97
131
|
export const pressChunk = (state, chunk) => keysOf(chunk).reduce(pressKey, state);
|
|
98
132
|
const widest = (choices) => choices.reduce((width, choice) => Math.max(width, choice.name.length), 0);
|
|
99
|
-
/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком в скобках. */
|
|
133
|
+
/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком или точкой в скобках. */
|
|
100
134
|
export function renderPicker(state, question) {
|
|
101
135
|
const width = widest(state.choices);
|
|
136
|
+
const help = state.single
|
|
137
|
+
? ' ↑↓ — двигаться, enter — готово'
|
|
138
|
+
: ' ↑↓ — двигаться, пробел — выбрать, a — все или ни одного, enter — готово';
|
|
102
139
|
return [
|
|
103
140
|
question,
|
|
104
|
-
|
|
141
|
+
help,
|
|
105
142
|
'',
|
|
106
143
|
...state.choices.map((choice, index) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
144
|
+
const marked = state.chosen.has(choice.id);
|
|
145
|
+
const mark = state.single ? (marked ? '(•)' : '( )') : marked ? '[x]' : '[ ]';
|
|
146
|
+
return `${index === state.cursor ? '❯' : ' '} ${mark} ${choice.name.padEnd(width)} ${choice.title}`;
|
|
109
147
|
}),
|
|
110
148
|
];
|
|
111
149
|
}
|
package/lib/picker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"picker.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Выбиралка галочками — её состояние и разметка, без единого касания терминала.\n *\n * Отделено от ввода-вывода по той же причине, что и команды: нажатие клавиши — это переход\n * состояния, и проверять его надо спекой, а не глазами на живом терминале. Сырой режим,\n * перерисовка и восстановление курсора живут на краю, в `bin/prompt.ts`, и не решают ничего.\n */\n\nexport interface IChoice {\n readonly id: string;\n /** Имя, которым ресурс называют в строке запуска: `access`. */\n readonly name: string;\n /** Заголовок ресурса — им человек и выбирает, имена ему ни о чём не говорят. */\n readonly title: string;\n}\n\nexport interface IPickerState {\n readonly choices: readonly IChoice[];\n readonly cursor: number;\n readonly chosen: ReadonlySet<string>;\n /** Выбор окончен, `chosen` — ответ. */\n readonly done: boolean;\n /** Выбор брошен: ответа нет, и подставлять вместо него умолчание нельзя. */\n readonly cancelled: boolean;\n}\n\n/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */\nexport function pickerOf(choices: readonly IChoice[]): IPickerState {\n return { choices, cursor: 0, chosen: new Set(choices.map((choice: IChoice): string => choice.id)), done: false, cancelled: false };\n}\n\n/**\n * Клавиши сырого потока. Записаны кодами, а не знаками: управляющий знак посреди исходника\n * невидим, и правка рядом с ним ломает разбор молча.\n *\n * Стрелка приходит целиком, тремя знаками, — сравнивать надо всю последовательность.\n */\nconst ESCAPE: string = String.fromCharCode(27);\n\nexport const KEY_UP: string = `${ESCAPE}[A`;\nexport const KEY_DOWN: string = `${ESCAPE}[B`;\nexport const KEY_ESCAPE: string = ESCAPE;\nexport const KEY_CANCEL: string = String.fromCharCode(3);\n\nconst UP: readonly string[] = [KEY_UP, 'k'];\nconst DOWN: readonly string[] = [KEY_DOWN, 'j'];\nconst DONE: readonly string[] = ['\\r', '\\n'];\n/** Ctrl-C и escape. Оба — «я передумал», и оба обязаны уйти без записи. */\nconst CANCEL: readonly string[] = [KEY_CANCEL, KEY_ESCAPE];\n\nfunction moved(state: IPickerState, step: number): IPickerState {\n const count: number = state.choices.length;\n\n return count ? { ...state, cursor: (state.cursor + step + count) % count } : state;\n}\n\nfunction toggled(state: IPickerState): IPickerState {\n const choice: IChoice | undefined = state.choices[state.cursor];\n if (!choice) {\n return state;\n }\n const chosen: Set<string> = new Set(state.chosen);\n if (!chosen.delete(choice.id)) {\n chosen.add(choice.id);\n }\n\n return { ...state, chosen };\n}\n\nfunction switched(state: IPickerState): IPickerState {\n const all: boolean = state.chosen.size === state.choices.length;\n\n return { ...state, chosen: all ? new Set() : new Set(state.choices.map((choice: IChoice): string => choice.id)) };\n}\n\n/**\n * Кусок сырого потока, разобранный на клавиши.\n *\n * Терминал не обещает по куску на нажатие: набранное быстро, вставленное из буфера и всё, что\n * пришло по конвейеру, приезжает одной строкой. Неразобранный кусок читался бы как одна\n * незнакомая клавиша, и выбиралка молча не отзывалась бы ни на что — так она и повисла на\n * первом прогоне через конвейер.\n *\n * Escape-последовательность берётся целиком: `ESC [ A` — это стрелка, а не escape и две буквы.\n */\nexport function keysOf(chunk: string): readonly string[] {\n const points: readonly string[] = Array.from(chunk);\n const keys: string[] = [];\n\n for (let index: number = 0; index < points.length; index++) {\n if (points[index] === ESCAPE && points[index + 1] === '[' && points[index + 2] !== undefined) {\n keys.push(points.slice(index, index + 3).join(''));\n index += 2;\n } else {\n keys.push(points[index]);\n }\n }\n\n return keys;\n}\n\n/** Переход состояния по одной нажатой клавише. Незнакомая клавиша не делает ничего. */\nexport function pressKey(state: IPickerState, key: string): IPickerState {\n if (state.done || state.cancelled) {\n return state;\n }\n if (UP.includes(key)) {\n return moved(state, -1);\n }\n if (DOWN.includes(key)) {\n return moved(state, 1);\n }\n if (CANCEL.includes(key)) {\n return { ...state, cancelled: true };\n }\n if (DONE.includes(key)) {\n return { ...state, done: true };\n }\n if (key === ' ') {\n return toggled(state);\n }\n if (key === 'a') {\n return switched(state);\n }\n\n return state;\n}\n\n/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */\nexport const pressChunk: (state: IPickerState, chunk: string) => IPickerState = (state: IPickerState, chunk: string): IPickerState =>\n keysOf(chunk).reduce(pressKey, state);\n\nconst widest: (choices: readonly IChoice[]) => number = (choices: readonly IChoice[]): number =>\n choices.reduce((width: number, choice: IChoice): number => Math.max(width, choice.name.length), 0);\n\n/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком в скобках. */\nexport function renderPicker(state: IPickerState, question: string): readonly string[] {\n const width: number = widest(state.choices);\n\n return [\n question,\n ' ↑↓ — двигаться, пробел — выбрать, a — все или ни одного, enter — готово',\n '',\n ...state.choices.map((choice: IChoice, index: number): string => {\n const mark: string = state.chosen.has(choice.id) ? 'x' : ' ';\n\n return `${index === state.cursor ? '❯' : ' '} [${mark}] ${choice.name.padEnd(width)} ${choice.title}`;\n }),\n ];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"picker.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Выбиралка галочками — её состояние и разметка, без единого касания терминала.\n *\n * Отделено от ввода-вывода по той же причине, что и команды: нажатие клавиши — это переход\n * состояния, и проверять его надо спекой, а не глазами на живом терминале. Сырой режим,\n * перерисовка и восстановление курсора живут на краю, в `bin/prompt.ts`, и не решают ничего.\n */\n\nexport interface IChoice {\n readonly id: string;\n /** Имя, которым ресурс называют в строке запуска: `access`. */\n readonly name: string;\n /** Заголовок ресурса — им человек и выбирает, имена ему ни о чём не говорят. */\n readonly title: string;\n}\n\nexport interface IPickerState {\n readonly choices: readonly IChoice[];\n readonly cursor: number;\n readonly chosen: ReadonlySet<string>;\n /**\n * Выбирается ровно один. Вид ресурса галочками не набирается: репозиторий лежит либо в\n * одном месте, либо в другом, и «оба» здесь означало бы два правила поставки в одном дереве.\n */\n readonly single: boolean;\n /** Выбор окончен, `chosen` — ответ. */\n readonly done: boolean;\n /** Выбор брошен: ответа нет, и подставлять вместо него умолчание нельзя. */\n readonly cancelled: boolean;\n}\n\n/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */\nexport function pickerOf(choices: readonly IChoice[]): IPickerState {\n return {\n choices,\n cursor: 0,\n chosen: new Set(choices.map((choice: IChoice): string => choice.id)),\n single: false,\n done: false,\n cancelled: false,\n };\n}\n\n/**\n * Выбор одного из. Отмечено то, что под курсором, и переезд курсора переносит отметку: так\n * ответ есть в любой момент, и enter отвечает без единого нажатия пробела.\n */\nexport function singlePickerOf(choices: readonly IChoice[]): IPickerState {\n return {\n choices,\n cursor: 0,\n chosen: new Set(choices.slice(0, 1).map((choice: IChoice): string => choice.id)),\n single: true,\n done: false,\n cancelled: false,\n };\n}\n\n/**\n * Клавиши сырого потока. Записаны кодами, а не знаками: управляющий знак посреди исходника\n * невидим, и правка рядом с ним ломает разбор молча.\n *\n * Стрелка приходит целиком, тремя знаками, — сравнивать надо всю последовательность.\n */\nconst ESCAPE: string = String.fromCharCode(27);\n\nexport const KEY_UP: string = `${ESCAPE}[A`;\nexport const KEY_DOWN: string = `${ESCAPE}[B`;\nexport const KEY_ESCAPE: string = ESCAPE;\nexport const KEY_CANCEL: string = String.fromCharCode(3);\n\nconst UP: readonly string[] = [KEY_UP, 'k'];\nconst DOWN: readonly string[] = [KEY_DOWN, 'j'];\nconst DONE: readonly string[] = ['\\r', '\\n'];\n/** Ctrl-C и escape. Оба — «я передумал», и оба обязаны уйти без записи. */\nconst CANCEL: readonly string[] = [KEY_CANCEL, KEY_ESCAPE];\n\nfunction moved(state: IPickerState, step: number): IPickerState {\n const count: number = state.choices.length;\n if (!count) {\n return state;\n }\n const cursor: number = (state.cursor + step + count) % count;\n const chosen: ReadonlySet<string> = state.single ? new Set([state.choices[cursor].id]) : state.chosen;\n\n return { ...state, cursor, chosen };\n}\n\nfunction toggled(state: IPickerState): IPickerState {\n const choice: IChoice | undefined = state.choices[state.cursor];\n if (!choice) {\n return state;\n }\n // При выборе одного пробел ставит отметку, но не снимает: отметить нечего — ответа не\n // осталось бы вовсе, а брошенный выбор здесь говорится отдельной клавишей.\n if (state.single) {\n return { ...state, chosen: new Set([choice.id]) };\n }\n const chosen: Set<string> = new Set(state.chosen);\n if (!chosen.delete(choice.id)) {\n chosen.add(choice.id);\n }\n\n return { ...state, chosen };\n}\n\nfunction switched(state: IPickerState): IPickerState {\n if (state.single) {\n return state;\n }\n const all: boolean = state.chosen.size === state.choices.length;\n\n return { ...state, chosen: all ? new Set() : new Set(state.choices.map((choice: IChoice): string => choice.id)) };\n}\n\n/**\n * Кусок сырого потока, разобранный на клавиши.\n *\n * Терминал не обещает по куску на нажатие: набранное быстро, вставленное из буфера и всё, что\n * пришло по конвейеру, приезжает одной строкой. Неразобранный кусок читался бы как одна\n * незнакомая клавиша, и выбиралка молча не отзывалась бы ни на что — так она и повисла на\n * первом прогоне через конвейер.\n *\n * Escape-последовательность берётся целиком: `ESC [ A` — это стрелка, а не escape и две буквы.\n */\nexport function keysOf(chunk: string): readonly string[] {\n const points: readonly string[] = Array.from(chunk);\n const keys: string[] = [];\n\n for (let index: number = 0; index < points.length; index++) {\n if (points[index] === ESCAPE && points[index + 1] === '[' && points[index + 2] !== undefined) {\n keys.push(points.slice(index, index + 3).join(''));\n index += 2;\n } else {\n keys.push(points[index]);\n }\n }\n\n return keys;\n}\n\n/** Переход состояния по одной нажатой клавише. Незнакомая клавиша не делает ничего. */\nexport function pressKey(state: IPickerState, key: string): IPickerState {\n if (state.done || state.cancelled) {\n return state;\n }\n if (UP.includes(key)) {\n return moved(state, -1);\n }\n if (DOWN.includes(key)) {\n return moved(state, 1);\n }\n if (CANCEL.includes(key)) {\n return { ...state, cancelled: true };\n }\n if (DONE.includes(key)) {\n return { ...state, done: true };\n }\n if (key === ' ') {\n return toggled(state);\n }\n if (key === 'a') {\n return switched(state);\n }\n\n return state;\n}\n\n/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */\nexport const pressChunk: (state: IPickerState, chunk: string) => IPickerState = (state: IPickerState, chunk: string): IPickerState =>\n keysOf(chunk).reduce(pressKey, state);\n\nconst widest: (choices: readonly IChoice[]) => number = (choices: readonly IChoice[]): number =>\n choices.reduce((width: number, choice: IChoice): number => Math.max(width, choice.name.length), 0);\n\n/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком или точкой в скобках. */\nexport function renderPicker(state: IPickerState, question: string): readonly string[] {\n const width: number = widest(state.choices);\n const help: string = state.single\n ? ' ↑↓ — двигаться, enter — готово'\n : ' ↑↓ — двигаться, пробел — выбрать, a — все или ни одного, enter — готово';\n\n return [\n question,\n help,\n '',\n ...state.choices.map((choice: IChoice, index: number): string => {\n const marked: boolean = state.chosen.has(choice.id);\n const mark: string = state.single ? (marked ? '(•)' : '( )') : marked ? '[x]' : '[ ]';\n\n return `${index === state.cursor ? '❯' : ' '} ${mark} ${choice.name.padEnd(width)} ${choice.title}`;\n }),\n ];\n}\n"]}
|
package/lib/sync.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { IGapOfVariant } from './catalog.js';
|
|
1
2
|
import { ICompanion } from './companion.js';
|
|
2
3
|
import { IConfig } from './config.js';
|
|
4
|
+
import { IHookBinding } from './hooks-map.js';
|
|
3
5
|
import { IPlanned } from './plan.js';
|
|
4
6
|
export interface ISyncResult {
|
|
5
7
|
readonly planned: readonly IPlanned[];
|
|
@@ -7,6 +9,30 @@ export interface ISyncResult {
|
|
|
7
9
|
readonly missing: ReadonlyMap<string, readonly string[]>;
|
|
8
10
|
/** Компаньоны разложенных правил: имена этого дерева, которые пишет проект. */
|
|
9
11
|
readonly companions: readonly ICompanion[];
|
|
12
|
+
/**
|
|
13
|
+
* Файлы, лежащие в дереве от ресурсов, от которых проект отказался.
|
|
14
|
+
*
|
|
15
|
+
* Отказ значит «больше не клади», а не «убери»: разложенное коммитится, рядом с правилом
|
|
16
|
+
* лежит написанный проектом компаньон, и стирать это молча пакет не вправе. Но и молчать
|
|
17
|
+
* нельзя — брошенный файл читается как действующее правило, и агент по нему работает.
|
|
18
|
+
*/
|
|
19
|
+
readonly abandoned: readonly string[];
|
|
20
|
+
/**
|
|
21
|
+
* Ресурсы, у которых виды есть, а вида под выбор дерева нет.
|
|
22
|
+
*
|
|
23
|
+
* Пропустить такой ресурс молча значит разложить правило и не разложить инструмент, который
|
|
24
|
+
* оно зовёт: дерево получает указание без исполнителя и узнаёт об этом, когда команда из
|
|
25
|
+
* правила не находится. Поэтому непустой список — отказ раскладки наравне с дыркой без
|
|
26
|
+
* значения и с файлом, который правили руками.
|
|
27
|
+
*/
|
|
28
|
+
readonly gaps: readonly IGapOfVariant[];
|
|
29
|
+
/**
|
|
30
|
+
* Разложенные гарды, которых нет в настройке агента: файл лежит, а позвать его некому.
|
|
31
|
+
*
|
|
32
|
+
* Раскладку это не отбивает — настройка принадлежит дереву, и пакет в неё не пишет, — но и
|
|
33
|
+
* молчать нельзя: гард, который не зовут, неотличим от гарда, который всё пропускает.
|
|
34
|
+
*/
|
|
35
|
+
readonly unbound: readonly IHookBinding[];
|
|
10
36
|
readonly written: readonly string[];
|
|
11
37
|
}
|
|
12
38
|
export declare function planSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult;
|
package/lib/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,UAAU,EAAyB,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAkC,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"AAWA,OAAO,EAAmB,aAAa,EAA4B,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,UAAU,EAAyB,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAC;AACrD,OAAO,EAAa,YAAY,EAAgB,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAkC,MAAM,WAAW,CAAC;AAMrE,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IACzD,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,SAAS,UAAU,EAAE,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AA2DD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CA6BvG;AAED,mGAAmG;AACnG,wBAAgB,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CA0CtG;AAED,eAAO,MAAM,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,SAAS,QAAQ,EACe,CAAC"}
|