@rt-tools/agent-kit 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -18
- package/assets/agents/business-analyst.md +74 -0
- package/assets/agents/project-manager.md +70 -0
- package/assets/agents/qa-engineer.md +72 -0
- package/assets/agents/skill-curator.md +110 -0
- package/assets/agents/spec-critic.md +44 -0
- package/assets/agents/spec-writer.md +50 -0
- package/assets/checks/board.github.mjs +286 -0
- package/assets/checks/check-board.github.mjs +188 -0
- package/assets/checks/check-doc-paths.mjs +163 -0
- package/assets/checks/check-dupes.mjs +277 -0
- package/assets/checks/check-lib-layers.mjs +573 -0
- package/assets/checks/check-reuse.mjs +208 -0
- package/assets/checks/check-schema-drift.mjs +186 -0
- package/assets/checks/check-specs.mjs +1007 -0
- package/assets/checks/check-styles.mjs +109 -0
- package/assets/checks/rt-kit-checks.config.mjs +134 -0
- package/assets/checks/task-new.github.mjs +198 -0
- package/assets/commands/skill-curator.md +70 -0
- package/assets/defaults/gate-map.sh +100 -0
- package/assets/defaults/project.sh +179 -0
- package/assets/hooks/browser-device-id.sh +20 -0
- package/assets/hooks/browser-guard-device-id.sh +28 -0
- package/assets/hooks/browser-guard-no-asking.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +18 -0
- package/assets/hooks/browser-guard-no-other-drivers.sh +79 -0
- package/assets/hooks/browser-guard-require-select.sh +54 -0
- package/assets/hooks/commit-msg.sh +26 -0
- package/assets/hooks/constitution-index.sh +43 -0
- package/assets/hooks/dev-server-guard.sh +115 -0
- package/assets/hooks/docs-guard.sh +282 -0
- package/assets/hooks/git-guard-delivery.sh +167 -0
- package/assets/hooks/git-guard-main.sh +73 -0
- package/assets/hooks/git-guard-push-tests.sh +94 -0
- package/assets/hooks/glossary-load.sh +23 -0
- package/assets/hooks/lint-after-edit.sh +219 -0
- package/assets/hooks/qa-dataid-guard.sh +121 -0
- package/assets/hooks/reuse-first-guard.sh +154 -0
- package/assets/hooks/skill-gate-rearm.sh +23 -0
- package/assets/hooks/skill-gate.sh +128 -0
- package/assets/hooks/skill-loaded.sh +21 -0
- package/assets/hooks/sql-guard.sh +679 -0
- package/assets/hooks/task-context-load.sh +100 -0
- package/assets/hooks/task-flow-guard.sh +107 -0
- package/assets/laws/{access.md → application/access.md} +1 -4
- package/assets/laws/{locales.md → application/locales.md} +1 -3
- package/assets/laws/application/money.md +41 -0
- package/assets/laws/application/ownership.md +32 -0
- package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
- package/assets/laws/code-structure.md +7 -6
- package/assets/laws/delivery.md +53 -3
- package/assets/laws/entity-editing.md +49 -55
- package/assets/laws/entity-models.md +4 -14
- package/assets/laws/frontend-application.md +5 -5
- package/assets/laws/lib-imports.md +14 -1
- package/assets/laws/lists.md +33 -0
- package/assets/laws/navigation.md +40 -0
- package/assets/laws/project-documentation.md +17 -8
- package/assets/laws/reuse-first.md +26 -21
- package/assets/laws/shared-code.md +13 -1
- package/assets/laws/verifiability.md +17 -1
- package/assets/laws/work-conduct.md +48 -0
- package/assets/patterns/admin-lists-screen.md +131 -0
- package/assets/patterns/admin-nav-item.md +71 -0
- package/assets/patterns/angular-patterns-state.md +101 -0
- package/assets/patterns/api-layer-pair.md +88 -0
- package/assets/patterns/browser-verification-measure.md +86 -0
- package/assets/patterns/browser-verification-stand.md +143 -0
- package/assets/patterns/component-structure-new.md +99 -0
- package/assets/patterns/dependencies-upgrade.md +65 -0
- package/assets/patterns/doc-style-sweep.md +137 -0
- package/assets/patterns/doc-style-write.md +109 -0
- package/assets/patterns/entity-aside.md +136 -0
- package/assets/patterns/entity-models-new.md +124 -0
- package/assets/patterns/entity-store.md +91 -0
- package/assets/patterns/git-workflow-commit.azure.md +259 -0
- package/assets/patterns/git-workflow-commit.github.md +333 -0
- package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
- package/assets/patterns/git-workflow-merge.md +99 -0
- package/assets/patterns/git-workflow-migration.md +88 -0
- package/assets/patterns/git-workflow-restart.md +49 -0
- package/assets/patterns/lib-layers-move.md +95 -0
- package/assets/patterns/lib-layers-new.md +82 -0
- package/assets/patterns/ownership-scope-resolve.md +69 -0
- package/assets/patterns/permissions-procedure.md +71 -0
- package/assets/patterns/platform-access-di.md +84 -0
- package/assets/patterns/pricing-quote.md +71 -0
- package/assets/patterns/reuse-first-extend.md +73 -0
- package/assets/patterns/seo-page.md +104 -0
- package/assets/patterns/seo-verify.md +83 -0
- package/assets/patterns/shared-code-new.md +86 -0
- package/assets/patterns/spec-driven-domain.md +107 -0
- package/assets/patterns/spec-driven-rule.md +127 -0
- package/assets/patterns/styling-bem-component.md +88 -0
- package/assets/patterns/styling-bem-layout.md +73 -0
- package/assets/patterns/task-flow-close.md +90 -0
- package/assets/patterns/task-flow-resume.md +94 -0
- package/assets/patterns/task-flow-start.md +117 -0
- package/assets/patterns/testing-e2e.md +92 -0
- package/assets/patterns/testing-unit.md +117 -0
- package/assets/patterns/translations-key.md +64 -0
- package/assets/patterns/ts-procedure.md +65 -0
- package/assets/rules/angular-patterns.md +71 -0
- package/assets/rules/api-layer.md +71 -0
- package/assets/rules/browser-verification.md +87 -0
- package/assets/rules/component-structure.md +64 -0
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +103 -0
- package/assets/rules/entity-conventions.md +78 -0
- package/assets/rules/entity-models.md +70 -0
- package/assets/rules/git-workflow.azure.md +116 -0
- package/assets/rules/git-workflow.github.md +123 -0
- package/assets/rules/git-workflow.gitlab.md +113 -0
- package/assets/rules/lib-layers.md +80 -0
- package/assets/rules/lists.md +73 -0
- package/assets/rules/navigation.md +78 -0
- package/assets/rules/ownership-scope.md +63 -0
- package/assets/rules/permissions.md +70 -0
- package/assets/rules/platform-access.md +77 -0
- package/assets/rules/pricing.md +64 -0
- package/assets/rules/reuse-first.md +83 -0
- package/assets/rules/seo.md +71 -0
- package/assets/rules/shared-code.md +70 -0
- package/assets/rules/spec-driven.md +135 -0
- package/assets/rules/styling-bem.md +74 -0
- package/assets/rules/task-flow.md +110 -0
- package/assets/rules/testing.md +100 -0
- package/assets/rules/translations.md +69 -0
- package/assets/rules/typescript-conventions.md +76 -0
- package/assets/skills/agent-kit.md +81 -0
- package/assets/skills/write-a-skill.md +108 -0
- package/assets/templates/gate-map.sh +45 -0
- package/assets/templates/implementation.md +44 -0
- package/assets/templates/pattern.md +5 -1
- package/assets/templates/project.sh +54 -0
- package/assets/templates/rule.md +12 -23
- package/assets/variants.json +20 -0
- package/assets/workflows/feature.js +134 -0
- package/assets/workflows/plan.js +150 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +78 -5
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +5 -0
- package/bin/prompt.d.ts.map +1 -1
- package/bin/prompt.js +19 -7
- package/bin/prompt.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +14 -1
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +23 -2
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +52 -5
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +104 -16
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +22 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +218 -11
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +57 -0
- package/lib/companion.d.ts.map +1 -0
- package/lib/companion.js +60 -0
- package/lib/companion.js.map +1 -0
- package/lib/config.d.ts +42 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +60 -2
- package/lib/config.js.map +1 -1
- package/lib/freshness.d.ts +14 -0
- package/lib/freshness.d.ts.map +1 -0
- package/lib/freshness.js +116 -0
- package/lib/freshness.js.map +1 -0
- package/lib/hooks-map.d.ts +24 -0
- package/lib/hooks-map.d.ts.map +1 -0
- package/lib/hooks-map.js +72 -0
- package/lib/hooks-map.js.map +1 -0
- package/lib/integrity.d.ts +36 -0
- package/lib/integrity.d.ts.map +1 -0
- package/lib/integrity.js +44 -0
- package/lib/integrity.js.map +1 -0
- package/lib/picker.d.ts +11 -1
- package/lib/picker.d.ts.map +1 -1
- package/lib/picker.js +44 -6
- package/lib/picker.js.map +1 -1
- package/lib/stamp.d.ts +2 -5
- package/lib/stamp.d.ts.map +1 -1
- package/lib/stamp.js +25 -10
- package/lib/stamp.js.map +1 -1
- package/lib/sync.d.ts +29 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +78 -4
- package/lib/sync.js.map +1 -1
- package/lib/variants.d.ts +44 -0
- package/lib/variants.d.ts.map +1 -0
- package/lib/variants.js +82 -0
- package/lib/variants.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.4.0.tgz +0 -0
- package/assets/laws/admin-lists.md +0 -35
- package/assets/laws/admin-navigation.md +0 -38
- package/rt-tools-agent-kit-0.2.0.tgz +0 -0
package/lib/config.d.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */
|
|
2
|
-
export type TKind = 'laws' | 'hooks' | 'checks' | 'agents' | 'templates';
|
|
2
|
+
export type TKind = 'laws' | 'rules' | 'patterns' | 'skills' | 'hooks' | 'defaults' | 'checks' | 'agents' | 'commands' | 'workflows' | 'templates';
|
|
3
3
|
export declare const KINDS: readonly TKind[];
|
|
4
|
+
/**
|
|
5
|
+
* Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как
|
|
6
|
+
* `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.
|
|
7
|
+
*
|
|
8
|
+
* `skills` — скил, у которого нет закона над собой: он не про то, что должно быть верно в
|
|
9
|
+
* продукте, а про то, как здесь делается работа. Правилом его называть нельзя — тогда придётся
|
|
10
|
+
* выдумать ему закон, — а паттерном тем более: паттерн стоит при правиле.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SKILL_KINDS: readonly TKind[];
|
|
13
|
+
/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */
|
|
14
|
+
export declare const SKILL_FILE: string;
|
|
15
|
+
/**
|
|
16
|
+
* Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.
|
|
17
|
+
*
|
|
18
|
+
* Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один
|
|
19
|
+
* раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.
|
|
20
|
+
*/
|
|
21
|
+
export declare const COMPANION_FILE: string;
|
|
4
22
|
export interface IConfig {
|
|
5
23
|
/** Значения дырок `{{имя}}`. */
|
|
6
24
|
readonly vars: Readonly<Record<string, string>>;
|
|
@@ -11,11 +29,33 @@ export interface IConfig {
|
|
|
11
29
|
* Ограничивает только те роды, которые сам называет; как именно — `isChosen`.
|
|
12
30
|
*/
|
|
13
31
|
readonly only: readonly string[];
|
|
14
|
-
/** Ресурсы, от которых проект отказался, идентификаторами: `laws/money.md`. */
|
|
32
|
+
/** Ресурсы, от которых проект отказался, идентификаторами: `laws/application/money.md`. */
|
|
15
33
|
readonly skip: readonly string[];
|
|
34
|
+
/**
|
|
35
|
+
* Выбор проекта по осям различия: `{ "host": "gitlab" }`. Оси объявлены пакетом в
|
|
36
|
+
* `assets/variants.json`, а ресурс с вариантом в имени берётся, только если проект выбрал
|
|
37
|
+
* его значение; как именно — `isChosen`.
|
|
38
|
+
*/
|
|
39
|
+
readonly variants: Readonly<Record<string, string>>;
|
|
16
40
|
}
|
|
17
41
|
export declare const CONFIG_PATH: string;
|
|
18
42
|
export declare const OVERRIDES_DIR: string;
|
|
43
|
+
/**
|
|
44
|
+
* Карта «что правится — какое правило» и профиль дерева: команды, стенды, пары «правка —
|
|
45
|
+
* документ». Оба лежат в двух видах, и это не дублирование, а разделение ответственности.
|
|
46
|
+
*
|
|
47
|
+
* Умолчание везёт пакет: проекты этой мастерской устроены одинаково — Nx, те же расширения, те
|
|
48
|
+
* же имена каталогов, — и переписывать одну и ту же карту в каждом дереве заново значило бы
|
|
49
|
+
* заводить пятнадцать её редакций, расходящихся молча. Оно разложено, под шапкой, и правится
|
|
50
|
+
* не на месте.
|
|
51
|
+
*
|
|
52
|
+
* Надстройку пишет проект, и она необязательна: витрина, свой род файлов, другой запускатель
|
|
53
|
+
* тестов есть не у всех. Объявленная в ней функция замещает умолчание целиком и вправе позвать
|
|
54
|
+
* его обратно суффиксом `_default` — так дерево дописывает своё, не теряя общего.
|
|
55
|
+
*/
|
|
56
|
+
export declare const DEFAULTS_DIR: string;
|
|
57
|
+
export declare const GATE_MAP_FILE: string;
|
|
58
|
+
export declare const PROFILE_FILE: string;
|
|
19
59
|
/**
|
|
20
60
|
* Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:
|
|
21
61
|
* агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет
|
package/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"AAUA,2EAA2E;AAC3E,MAAM,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"AAUA,2EAA2E;AAC3E,MAAM,MAAM,KAAK,GACb,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;AAEnI,eAAO,MAAM,KAAK,EAAE,SAAS,KAAK,EAYjC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,KAAK,EAAoC,CAAC;AAE7E,iFAAiF;AACjF,eAAO,MAAM,UAAU,EAAE,MAAmB,CAAC;AAE7C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,MAA4B,CAAC;AAE1D,MAAM,WAAW,OAAO;IACpB,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,2FAA2F;IAC3F,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD;AAED,eAAO,MAAM,WAAW,EAAE,MAA8B,CAAC;AACzD,eAAO,MAAM,aAAa,EAAE,MAAmC,CAAC;AAEhE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,EAAE,MAAkC,CAAC;AAC9D,eAAO,MAAM,aAAa,EAAE,MAAsB,CAAC;AACnD,eAAO,MAAM,YAAY,EAAE,MAAqB,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAY1D,CAAC;AAEF,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAgCzC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAmCjD;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAUvD"}
|
package/lib/config.js
CHANGED
|
@@ -7,9 +7,55 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { readFileSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
-
export const KINDS = [
|
|
10
|
+
export const KINDS = [
|
|
11
|
+
'laws',
|
|
12
|
+
'rules',
|
|
13
|
+
'patterns',
|
|
14
|
+
'skills',
|
|
15
|
+
'hooks',
|
|
16
|
+
'defaults',
|
|
17
|
+
'checks',
|
|
18
|
+
'agents',
|
|
19
|
+
'commands',
|
|
20
|
+
'workflows',
|
|
21
|
+
'templates',
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как
|
|
25
|
+
* `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.
|
|
26
|
+
*
|
|
27
|
+
* `skills` — скил, у которого нет закона над собой: он не про то, что должно быть верно в
|
|
28
|
+
* продукте, а про то, как здесь делается работа. Правилом его называть нельзя — тогда придётся
|
|
29
|
+
* выдумать ему закон, — а паттерном тем более: паттерн стоит при правиле.
|
|
30
|
+
*/
|
|
31
|
+
export const SKILL_KINDS = ['rules', 'patterns', 'skills'];
|
|
32
|
+
/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */
|
|
33
|
+
export const SKILL_FILE = 'SKILL.md';
|
|
34
|
+
/**
|
|
35
|
+
* Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.
|
|
36
|
+
*
|
|
37
|
+
* Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один
|
|
38
|
+
* раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.
|
|
39
|
+
*/
|
|
40
|
+
export const COMPANION_FILE = 'implementation.md';
|
|
11
41
|
export const CONFIG_PATH = '.claude/rt-kit.json';
|
|
12
42
|
export const OVERRIDES_DIR = '.claude/rt-kit/overrides';
|
|
43
|
+
/**
|
|
44
|
+
* Карта «что правится — какое правило» и профиль дерева: команды, стенды, пары «правка —
|
|
45
|
+
* документ». Оба лежат в двух видах, и это не дублирование, а разделение ответственности.
|
|
46
|
+
*
|
|
47
|
+
* Умолчание везёт пакет: проекты этой мастерской устроены одинаково — Nx, те же расширения, те
|
|
48
|
+
* же имена каталогов, — и переписывать одну и ту же карту в каждом дереве заново значило бы
|
|
49
|
+
* заводить пятнадцать её редакций, расходящихся молча. Оно разложено, под шапкой, и правится
|
|
50
|
+
* не на месте.
|
|
51
|
+
*
|
|
52
|
+
* Надстройку пишет проект, и она необязательна: витрина, свой род файлов, другой запускатель
|
|
53
|
+
* тестов есть не у всех. Объявленная в ней функция замещает умолчание целиком и вправе позвать
|
|
54
|
+
* его обратно суффиксом `_default` — так дерево дописывает своё, не теряя общего.
|
|
55
|
+
*/
|
|
56
|
+
export const DEFAULTS_DIR = '.claude/rt-kit/defaults';
|
|
57
|
+
export const GATE_MAP_FILE = 'gate-map.sh';
|
|
58
|
+
export const PROFILE_FILE = 'project.sh';
|
|
13
59
|
/**
|
|
14
60
|
* Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:
|
|
15
61
|
* агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет
|
|
@@ -17,9 +63,15 @@ export const OVERRIDES_DIR = '.claude/rt-kit/overrides';
|
|
|
17
63
|
*/
|
|
18
64
|
export const DEFAULT_LAYOUT = {
|
|
19
65
|
laws: 'docs/constitution',
|
|
66
|
+
rules: '.claude/skills',
|
|
67
|
+
patterns: '.claude/skills',
|
|
68
|
+
skills: '.claude/skills',
|
|
20
69
|
hooks: '.claude/hooks',
|
|
70
|
+
defaults: DEFAULTS_DIR,
|
|
21
71
|
checks: 'tools',
|
|
22
72
|
agents: '.claude/agents',
|
|
73
|
+
commands: '.claude/commands',
|
|
74
|
+
workflows: '.claude/workflows',
|
|
23
75
|
templates: '.claude/rt-kit/templates',
|
|
24
76
|
};
|
|
25
77
|
export class ConfigError extends Error {
|
|
@@ -73,7 +125,13 @@ export function parseConfig(text) {
|
|
|
73
125
|
for (const kind of KINDS) {
|
|
74
126
|
derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];
|
|
75
127
|
}
|
|
76
|
-
return {
|
|
128
|
+
return {
|
|
129
|
+
vars: { ...derived, ...stringMap(raw['vars'], 'vars') },
|
|
130
|
+
layout: layout,
|
|
131
|
+
only,
|
|
132
|
+
skip,
|
|
133
|
+
variants: stringMap(raw['variants'], 'variants'),
|
|
134
|
+
};
|
|
77
135
|
}
|
|
78
136
|
/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */
|
|
79
137
|
export function readConfig(root) {
|
package/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind = 'laws' | 'hooks' | 'checks' | 'agents' | 'templates';\n\nexport const KINDS: readonly TKind[] = ['laws', 'hooks', 'checks', 'agents', 'templates'];\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /**\n * Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.\n * Ограничивает только те роды, которые сам называет; как именно — `isChosen`.\n */\n readonly only: readonly string[];\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/money.md`. */\n readonly skip: readonly string[];\n}\n\nexport const CONFIG_PATH: string = '.claude/rt-kit.json';\nexport const OVERRIDES_DIR: string = '.claude/rt-kit/overrides';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n hooks: '.claude/hooks',\n checks: 'tools',\n agents: '.claude/agents',\n templates: '.claude/rt-kit/templates',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\nfunction idList(value: unknown, where: string): readonly string[] {\n const list: unknown = value ?? [];\n if (!Array.isArray(list) || list.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);\n }\n\n return list as string[];\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const only: readonly string[] = idList(raw['only'], 'only');\n const skip: readonly string[] = idList(raw['skip'], 'skip');\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n\n return { vars: { ...derived, ...stringMap(raw['vars'], 'vars') }, layout: layout as Record<TKind, string>, only, skip };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind =\n 'laws' | 'rules' | 'patterns' | 'skills' | 'hooks' | 'defaults' | 'checks' | 'agents' | 'commands' | 'workflows' | 'templates';\n\nexport const KINDS: readonly TKind[] = [\n 'laws',\n 'rules',\n 'patterns',\n 'skills',\n 'hooks',\n 'defaults',\n 'checks',\n 'agents',\n 'commands',\n 'workflows',\n 'templates',\n];\n\n/**\n * Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как\n * `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.\n *\n * `skills` — скил, у которого нет закона над собой: он не про то, что должно быть верно в\n * продукте, а про то, как здесь делается работа. Правилом его называть нельзя — тогда придётся\n * выдумать ему закон, — а паттерном тем более: паттерн стоит при правиле.\n */\nexport const SKILL_KINDS: readonly TKind[] = ['rules', 'patterns', 'skills'];\n\n/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */\nexport const SKILL_FILE: string = 'SKILL.md';\n\n/**\n * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.\n *\n * Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один\n * раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.\n */\nexport const COMPANION_FILE: string = 'implementation.md';\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /**\n * Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.\n * Ограничивает только те роды, которые сам называет; как именно — `isChosen`.\n */\n readonly only: readonly string[];\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/application/money.md`. */\n readonly skip: readonly string[];\n /**\n * Выбор проекта по осям различия: `{ \"host\": \"gitlab\" }`. Оси объявлены пакетом в\n * `assets/variants.json`, а ресурс с вариантом в имени берётся, только если проект выбрал\n * его значение; как именно — `isChosen`.\n */\n readonly variants: Readonly<Record<string, string>>;\n}\n\nexport const CONFIG_PATH: string = '.claude/rt-kit.json';\nexport const OVERRIDES_DIR: string = '.claude/rt-kit/overrides';\n\n/**\n * Карта «что правится — какое правило» и профиль дерева: команды, стенды, пары «правка —\n * документ». Оба лежат в двух видах, и это не дублирование, а разделение ответственности.\n *\n * Умолчание везёт пакет: проекты этой мастерской устроены одинаково — Nx, те же расширения, те\n * же имена каталогов, — и переписывать одну и ту же карту в каждом дереве заново значило бы\n * заводить пятнадцать её редакций, расходящихся молча. Оно разложено, под шапкой, и правится\n * не на месте.\n *\n * Надстройку пишет проект, и она необязательна: витрина, свой род файлов, другой запускатель\n * тестов есть не у всех. Объявленная в ней функция замещает умолчание целиком и вправе позвать\n * его обратно суффиксом `_default` — так дерево дописывает своё, не теряя общего.\n */\nexport const DEFAULTS_DIR: string = '.claude/rt-kit/defaults';\nexport const GATE_MAP_FILE: string = 'gate-map.sh';\nexport const PROFILE_FILE: string = 'project.sh';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n rules: '.claude/skills',\n patterns: '.claude/skills',\n skills: '.claude/skills',\n hooks: '.claude/hooks',\n defaults: DEFAULTS_DIR,\n checks: 'tools',\n agents: '.claude/agents',\n commands: '.claude/commands',\n workflows: '.claude/workflows',\n templates: '.claude/rt-kit/templates',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\nfunction idList(value: unknown, where: string): readonly string[] {\n const list: unknown = value ?? [];\n if (!Array.isArray(list) || list.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);\n }\n\n return list as string[];\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const only: readonly string[] = idList(raw['only'], 'only');\n const skip: readonly string[] = idList(raw['skip'], 'skip');\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n\n return {\n vars: { ...derived, ...stringMap(raw['vars'], 'vars') },\n layout: layout as Record<TKind, string>,\n only,\n skip,\n variants: stringMap(raw['variants'], 'variants'),\n };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface IStaleBuild {
|
|
2
|
+
/** Каталог ресурсов в исходниках. */
|
|
3
|
+
readonly source: string;
|
|
4
|
+
/** Файл ресурса, правленный позже сборки, — по нему видно, чего именно не хватает в дереве. */
|
|
5
|
+
readonly newest: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Правлены ли ресурсы позже, чем собран пакет, из которого идёт раскладка.
|
|
9
|
+
*
|
|
10
|
+
* `null` значит «сверять не с чем или сверка сошлась»: у потребителя исходников нет вовсе, а у
|
|
11
|
+
* того, кто запускает пакет прямо из исходников, собранное и исходное — один и тот же каталог.
|
|
12
|
+
*/
|
|
13
|
+
export declare function staleBuild(packageRoot: string, name: string): IStaleBuild | null;
|
|
14
|
+
//# sourceMappingURL=freshness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/freshness.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,WAAW;IACxB,qCAAqC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B;AAmFD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAiBhF"}
|
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"]}
|