@rt-tools/agent-kit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +59 -6
  2. package/assets/hooks/browser-device-id.sh +20 -0
  3. package/assets/hooks/browser-guard-device-id.sh +27 -0
  4. package/assets/hooks/browser-guard-no-listing.sh +17 -0
  5. package/assets/hooks/browser-guard-no-other-drivers.sh +78 -0
  6. package/assets/hooks/browser-guard-require-select.sh +53 -0
  7. package/assets/hooks/commit-msg.sh +26 -0
  8. package/assets/hooks/constitution-index.sh +42 -0
  9. package/assets/hooks/dev-server-guard.sh +113 -0
  10. package/assets/hooks/docs-guard.sh +96 -0
  11. package/assets/hooks/git-guard-delivery.sh +110 -0
  12. package/assets/hooks/git-guard-main.sh +72 -0
  13. package/assets/hooks/git-guard-push-tests.sh +73 -0
  14. package/assets/hooks/lint-after-edit.sh +94 -0
  15. package/assets/hooks/qa-dataid-guard.sh +81 -0
  16. package/assets/hooks/reuse-first-guard.sh +83 -0
  17. package/assets/hooks/skill-gate-rearm.sh +22 -0
  18. package/assets/hooks/skill-gate.sh +68 -0
  19. package/assets/hooks/skill-loaded.sh +20 -0
  20. package/assets/hooks/sql-guard.sh +129 -0
  21. package/assets/patterns/angular-patterns-state.md +94 -0
  22. package/assets/patterns/api-layer-pair.md +78 -0
  23. package/assets/patterns/browser-verification-measure.md +83 -0
  24. package/assets/patterns/browser-verification-stand.md +79 -0
  25. package/assets/patterns/component-structure-new.md +98 -0
  26. package/assets/patterns/doc-style-sweep.md +100 -0
  27. package/assets/patterns/doc-style-write.md +106 -0
  28. package/assets/patterns/git-workflow-commit.md +175 -0
  29. package/assets/patterns/git-workflow-merge.md +82 -0
  30. package/assets/patterns/git-workflow-migration.md +58 -0
  31. package/assets/patterns/git-workflow-restart.md +49 -0
  32. package/assets/patterns/lib-layers-move.md +77 -0
  33. package/assets/patterns/lib-layers-new.md +70 -0
  34. package/assets/patterns/permissions-procedure.md +69 -0
  35. package/assets/patterns/platform-access-di.md +70 -0
  36. package/assets/patterns/reuse-first-extend.md +73 -0
  37. package/assets/patterns/seo-page.md +92 -0
  38. package/assets/patterns/seo-verify.md +64 -0
  39. package/assets/patterns/shared-code-new.md +80 -0
  40. package/assets/patterns/spec-driven-domain.md +100 -0
  41. package/assets/patterns/spec-driven-rule.md +112 -0
  42. package/assets/patterns/styling-bem-component.md +77 -0
  43. package/assets/patterns/styling-bem-layout.md +67 -0
  44. package/assets/patterns/testing-e2e.md +90 -0
  45. package/assets/patterns/testing-unit.md +93 -0
  46. package/assets/patterns/translations-key.md +51 -0
  47. package/assets/patterns/ts-procedure.md +66 -0
  48. package/assets/rules/angular-patterns.md +52 -0
  49. package/assets/rules/api-layer.md +53 -0
  50. package/assets/rules/browser-verification.md +69 -0
  51. package/assets/rules/component-structure.md +48 -0
  52. package/assets/rules/doc-style.md +61 -0
  53. package/assets/rules/git-workflow.md +106 -0
  54. package/assets/rules/lib-layers.md +54 -0
  55. package/assets/rules/permissions.md +52 -0
  56. package/assets/rules/platform-access.md +49 -0
  57. package/assets/rules/reuse-first.md +69 -0
  58. package/assets/rules/seo.md +50 -0
  59. package/assets/rules/shared-code.md +45 -0
  60. package/assets/rules/spec-driven.md +89 -0
  61. package/assets/rules/styling-bem.md +59 -0
  62. package/assets/rules/testing.md +69 -0
  63. package/assets/rules/translations.md +52 -0
  64. package/assets/rules/typescript-conventions.md +46 -0
  65. package/assets/templates/gate-map.sh +37 -0
  66. package/assets/templates/implementation.md +38 -0
  67. package/assets/templates/pattern.md +4 -0
  68. package/assets/templates/project.sh +41 -0
  69. package/assets/templates/rule.md +12 -23
  70. package/lib/assets.d.ts +8 -0
  71. package/lib/assets.d.ts.map +1 -1
  72. package/lib/assets.js +12 -1
  73. package/lib/assets.js.map +1 -1
  74. package/lib/commands.d.ts.map +1 -1
  75. package/lib/commands.js +21 -2
  76. package/lib/commands.js.map +1 -1
  77. package/lib/companion.d.ts +53 -0
  78. package/lib/companion.d.ts.map +1 -0
  79. package/lib/companion.js +33 -0
  80. package/lib/companion.js.map +1 -0
  81. package/lib/config.d.ts +24 -1
  82. package/lib/config.d.ts.map +1 -1
  83. package/lib/config.js +33 -1
  84. package/lib/config.js.map +1 -1
  85. package/lib/stamp.d.ts +2 -5
  86. package/lib/stamp.d.ts.map +1 -1
  87. package/lib/stamp.js +25 -10
  88. package/lib/stamp.js.map +1 -1
  89. package/lib/sync.d.ts +3 -0
  90. package/lib/sync.d.ts.map +1 -1
  91. package/lib/sync.js +20 -1
  92. package/lib/sync.js.map +1 -1
  93. package/package.json +1 -1
  94. package/rt-tools-agent-kit-0.3.0.tgz +0 -0
  95. package/rt-tools-agent-kit-0.2.0.tgz +0 -0
@@ -0,0 +1,38 @@
1
+ # <имя-правила> — как это устроено здесь
2
+
3
+ Имена этого дерева при правиле `SKILL.md` рядом. Отдельный файл потому, что правило говорит
4
+ приёмом и переносится между репозиториями целиком, а всё, что ниже, верно только здесь и
5
+ устаревает при каждом переименовании.
6
+
7
+ Пока в файле стоит `<!-- заполняет проект -->`, правило считается неразвёрнутым: `agent-kit
8
+ sync --check` отказывает, а агент читает указание, которому здесь нечего назвать.
9
+
10
+ ## Как это называется здесь
11
+
12
+ | В правиле | Здесь |
13
+ | ----------------- | ------------------------------------------- |
14
+ | <понятие правила> | <имя в этом коде> <!-- заполняет проект --> |
15
+
16
+ ## Где это лежит
17
+
18
+ | Что | Где |
19
+ | ---------- | ---------------------------------- |
20
+ | <механизм> | `<путь>` <!-- заполняет проект --> |
21
+
22
+ ## Где исполняются статьи
23
+
24
+ Первая колонка — статья дословно, как она написана в разделе «Что здесь действует» правила
25
+ (жирная часть пункта). Статья без строки и строка без статьи — расхождение: правило обещает
26
+ то, чего в дереве нет, либо в дереве стоит то, о чём правило молчит.
27
+
28
+ | Статья | Где исполняется |
29
+ | ----------------- | ------------------------------------------- |
30
+ | <статья дословно> | `<путь>:<символ>` <!-- заполняет проект --> |
31
+
32
+ ## Что ещё стоит знать при чтении кода
33
+
34
+ - <Что видно только изнутри этого дерева: почему приём выглядит здесь именно так.>
35
+
36
+ ## Чем это проверяется
37
+
38
+ - <Команда, которая ловит нарушение, — и что именно она ловит.>
@@ -18,6 +18,10 @@ description: Паттерн правила <имя-правила>. Брать <
18
18
 
19
19
  <Готовый код целиком, а не пересказ: паттерн берут, чтобы не писать заново.>
20
20
 
21
+ Имена в примере безымянные — `libs/<домен>`, `<Feature>Component`, `<app>`. Настоящие имена
22
+ этого дерева стоят в `implementation.md` при правиле: пример, написанный на чужих именах,
23
+ читается как рабочий код и переносится в дерево вместе с ними.
24
+
21
25
  ```
22
26
  <код>
23
27
  ```
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ # Профиль этого дерева: что здесь чем зовётся и какими командами проверяется.
3
+ #
4
+ # Живёт в проекте, а не в пакете: механизм у сторожевых хуков общий, а команды, порты и пары
5
+ # «правка — её документ» у каждого дерева свои. Пакет везёт хуки, проект — этот файл.
6
+ #
7
+ # Каждая функция вправе промолчать. Молчание значит «правила на это нет», и хук пропускает:
8
+ # пустой профиль оставляет хуки безвредными, а не отбивающими наугад.
9
+
10
+ # Где подняты приложения. Идёт в текст отказа, когда кто-то поднимает второй экземпляр.
11
+ RT_STANDS='<приложение> http://localhost:<порт>, <приложение> http://localhost:<порт>'
12
+
13
+ # Команды, которые обязаны пройти перед пушем. По одной на строку; первая упавшая отбивает пуш.
14
+ rt_push_checks() {
15
+ cat <<'EOF'
16
+ <команда линтера кода>
17
+ <команда линтера стилей>
18
+ <команда тестов>
19
+ EOF
20
+ }
21
+
22
+ # Какой документ обязан ехать тем же коммитом, что и этот файл. Печатает путь или молчит.
23
+ rt_docs_pair_for() {
24
+ case "$1" in
25
+ */<каталог правил>/*) printf '%s' '<зеркало правила>' ;;
26
+ *.proto) printf '%s' '<спек задетого домена>' ;;
27
+ esac
28
+ }
29
+
30
+ # Чем линтуется этот файл сразу после правки. Печатает команду или молчит.
31
+ rt_lint_for() {
32
+ case "$1" in
33
+ *.scss) printf '%s' '<команда линтера стилей> "$1"' ;;
34
+ *.ts) printf '%s' '<команда линтера кода> "$1"' ;;
35
+ esac
36
+ }
37
+
38
+ # Имя ветки, с которой разрешено открывать PR: номер задачи в имени. Успех — годится.
39
+ rt_task_branch_ok() {
40
+ printf '%s' "$1" | grep -qE '<выражение имени ветки>'
41
+ }
@@ -2,37 +2,26 @@
2
2
  name: <имя-правила>
3
3
  kind: rule
4
4
  law: <закон>
5
- description: Правило проекта под закон «<Название закона>». Брать при <когда> — <что называет>. Готовый код — в паттернах <имена>.
5
+ description: Правило под закон «<Название закона>». Брать при <когда> — <каким приёмом это делается>. Готовый код — в паттернах <имена>.
6
6
  ---
7
7
 
8
- # <О чём закон> — как это устроено здесь
8
+ # <О чём закон> — каким приёмом
9
9
 
10
- Правило под закон `{{lawsDir}}/<закон>.md`. Закон говорит, что должно быть верно; здесь — чем
11
- это названо в этом дереве, где лежит и что из закона у нас не применяется.
10
+ Правило под закон `{{lawsDir}}/<закон>.md`. Закон говорит, что должно быть верно; здесь — каким
11
+ приёмом это делается. Чем это названо в этом дереве и где лежит `implementation.md` рядом:
12
+ правило переносится между репозиториями, имена — нет.
12
13
 
13
- ## Как это называется здесь
14
+ ## Когда берётся
14
15
 
15
- | В законе | Здесь |
16
- | ---------------- | ----------------- |
17
- | <понятие закона> | <имя в этом коде> |
16
+ <Правка, по которой правило узнаётся. Гейт зовёт его по этому признаку, а не по названию.>
18
17
 
19
- ## Где это лежит
18
+ ## Что здесь действует
20
19
 
21
- | Что | Где |
22
- | ---------- | -------- |
23
- | <механизм> | `<путь>` |
20
+ Каждый пункт начинается жирной статьёй, и у каждой статьи есть строка в `implementation.md`
21
+ рядом. Утверждение, которому места в коде не нашлось, сюда не ставится: оно уходит прозой в
22
+ «Ловушки» или статьёй в закон.
24
23
 
25
- ## Как закон применяется здесь
26
-
27
- Каждый пункт начинается жирной фразой, и у каждой жирной фразы есть строка в
28
- `implementation.md` рядом. Утверждение, которому места в коде не нашлось, сюда не ставится: оно
29
- уходит прозой в «Ловушки» или вопросом `Q-N` в закон.
30
-
31
- - **<Утверждение одной фразой.>** <Что сломается иначе — не больше двух предложений.>
32
-
33
- ## Чего из закона здесь нет
34
-
35
- <Что из закона этот проект не применяет и почему; ссылка на открытый вопрос закона.>
24
+ - **<Статья одной фразой.>** <Что сломается иначе — не больше двух предложений.>
36
25
 
37
26
  ## Паттерны
38
27
 
package/lib/assets.d.ts CHANGED
@@ -1,11 +1,19 @@
1
+ import { IEntryOfCatalog } from './catalog.js';
1
2
  import { IConfig, TKind } from './config.js';
2
3
  export interface IAsset {
3
4
  /** Идентификатор: `laws/delivery.md`. Он же путь надстройки и ключ в `only` и `skip`. */
4
5
  readonly id: string;
5
6
  readonly kind: TKind;
7
+ /** Имя без рода и расширения: `delivery`. У скила — имя каталога, которым его зовут. */
8
+ readonly name: string;
6
9
  readonly text: string;
7
10
  /** Путь в дереве проекта, куда ресурс ложится. */
8
11
  readonly target: string;
9
12
  }
13
+ /**
14
+ * Куда ложится ресурс. Скил читается агентом как `<имя>/SKILL.md`, и другого имени у него быть
15
+ * не может: файл, названный по ресурсу, агент не найдёт вовсе. Остальные роды ложатся файлом.
16
+ */
17
+ export declare function targetOf(entry: IEntryOfCatalog, layout: Readonly<Record<TKind, string>>): string;
10
18
  export declare function collectAssets(config: IConfig, assetsDir: string): readonly IAsset[];
11
19
  //# sourceMappingURL=assets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,MAAM;IACnB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CASnF"}
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,eAAe,EAAyB,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,OAAO,EAA2B,KAAK,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,WAAW,MAAM;IACnB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,wFAAwF;IACxF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAIhG;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAUnF"}
package/lib/assets.js CHANGED
@@ -7,14 +7,25 @@
7
7
  */
8
8
  import { join } from 'node:path';
9
9
  import { isChosen, readCatalog } from './catalog.js';
10
+ import { SKILL_FILE, SKILL_KINDS } from './config.js';
11
+ /**
12
+ * Куда ложится ресурс. Скил читается агентом как `<имя>/SKILL.md`, и другого имени у него быть
13
+ * не может: файл, названный по ресурсу, агент не найдёт вовсе. Остальные роды ложатся файлом.
14
+ */
15
+ export function targetOf(entry, layout) {
16
+ return SKILL_KINDS.includes(entry.kind)
17
+ ? join(layout[entry.kind], entry.name, SKILL_FILE)
18
+ : join(layout[entry.kind], entry.id.slice(entry.kind.length + 1));
19
+ }
10
20
  export function collectAssets(config, assetsDir) {
11
21
  return readCatalog(assetsDir)
12
22
  .filter((entry) => isChosen(entry, config.only, config.skip))
13
23
  .map((entry) => ({
14
24
  id: entry.id,
15
25
  kind: entry.kind,
26
+ name: entry.name,
16
27
  text: entry.text,
17
- target: join(config.layout[entry.kind], entry.id.slice(entry.kind.length + 1)),
28
+ target: targetOf(entry, config.layout),
18
29
  }));
19
30
  }
20
31
  //# sourceMappingURL=assets.js.map
package/lib/assets.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAmB,QAAQ,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAYtE,MAAM,UAAU,aAAa,CAAC,MAAe,EAAE,SAAiB;IAC5D,OAAO,WAAW,CAAC,SAAS,CAAC;SACxB,MAAM,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;SACtF,GAAG,CAAC,CAAC,KAAsB,EAAU,EAAE,CAAC,CAAC;QACtC,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;KACjF,CAAC,CAAC,CAAC;AACZ,CAAC","sourcesContent":["/**\n * Что из везомого пакетом взял этот проект и куда оно ложится.\n *\n * Полный набор читает `catalog.ts`, здесь из него остаётся выбранное. Разделено потому, что\n * выбирать приходится раньше, чем раскладывать: `init` спрашивает, какие законы взять, а `list`\n * показывает и то, от чего проект отказался, — обоим нужен набор до отбора.\n */\nimport { join } from 'node:path';\n\nimport { IEntryOfCatalog, isChosen, readCatalog } from './catalog.js';\nimport { IConfig, TKind } from './config.js';\n\nexport interface IAsset {\n /** Идентификатор: `laws/delivery.md`. Он же путь надстройки и ключ в `only` и `skip`. */\n readonly id: string;\n readonly kind: TKind;\n readonly text: string;\n /** Путь в дереве проекта, куда ресурс ложится. */\n readonly target: string;\n}\n\nexport function collectAssets(config: IConfig, assetsDir: string): readonly IAsset[] {\n return readCatalog(assetsDir)\n .filter((entry: IEntryOfCatalog): boolean => isChosen(entry, config.only, config.skip))\n .map((entry: IEntryOfCatalog): IAsset => ({\n id: entry.id,\n kind: entry.kind,\n text: entry.text,\n target: join(config.layout[entry.kind], entry.id.slice(entry.kind.length + 1)),\n }));\n}\n"]}
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAmB,QAAQ,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAW,UAAU,EAAE,WAAW,EAAS,MAAM,aAAa,CAAC;AAatE;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAsB,EAAE,MAAuC;IACpF,OAAO,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QACnC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC;QAClD,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAe,EAAE,SAAiB;IAC5D,OAAO,WAAW,CAAC,SAAS,CAAC;SACxB,MAAM,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;SACtF,GAAG,CAAC,CAAC,KAAsB,EAAU,EAAE,CAAC,CAAC;QACtC,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;KACzC,CAAC,CAAC,CAAC;AACZ,CAAC","sourcesContent":["/**\n * Что из везомого пакетом взял этот проект и куда оно ложится.\n *\n * Полный набор читает `catalog.ts`, здесь из него остаётся выбранное. Разделено потому, что\n * выбирать приходится раньше, чем раскладывать: `init` спрашивает, какие законы взять, а `list`\n * показывает и то, от чего проект отказался, — обоим нужен набор до отбора.\n */\nimport { join } from 'node:path';\n\nimport { IEntryOfCatalog, isChosen, readCatalog } from './catalog.js';\nimport { IConfig, SKILL_FILE, SKILL_KINDS, TKind } from './config.js';\n\nexport interface IAsset {\n /** Идентификатор: `laws/delivery.md`. Он же путь надстройки и ключ в `only` и `skip`. */\n readonly id: string;\n readonly kind: TKind;\n /** Имя без рода и расширения: `delivery`. У скила — имя каталога, которым его зовут. */\n readonly name: string;\n readonly text: string;\n /** Путь в дереве проекта, куда ресурс ложится. */\n readonly target: string;\n}\n\n/**\n * Куда ложится ресурс. Скил читается агентом как `<имя>/SKILL.md`, и другого имени у него быть\n * не может: файл, названный по ресурсу, агент не найдёт вовсе. Остальные роды ложатся файлом.\n */\nexport function targetOf(entry: IEntryOfCatalog, layout: Readonly<Record<TKind, string>>): string {\n return SKILL_KINDS.includes(entry.kind)\n ? join(layout[entry.kind], entry.name, SKILL_FILE)\n : join(layout[entry.kind], entry.id.slice(entry.kind.length + 1));\n}\n\nexport function collectAssets(config: IConfig, assetsDir: string): readonly IAsset[] {\n return readCatalog(assetsDir)\n .filter((entry: IEntryOfCatalog): boolean => isChosen(entry, config.only, config.skip))\n .map((entry: IEntryOfCatalog): IAsset => ({\n id: entry.id,\n kind: entry.kind,\n name: entry.name,\n text: entry.text,\n target: targetOf(entry, config.layout),\n }));\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/commands.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC9B;AAgDD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,SAAS,MAAM,EAAO,GAAG,iBAAiB,CAoBlF;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,iBAAiB,CA0CzE;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAyB3D;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CA+CzD"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/commands.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC9B;AA+DD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,SAAS,MAAM,EAAO,GAAG,iBAAiB,CAoBlF;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAgDzE;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAyB3D;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CA+CzD"}
package/lib/commands.js CHANGED
@@ -7,6 +7,7 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
7
7
  import { join } from 'node:path';
8
8
  import { collectAssets } from './assets.js';
9
9
  import { isChosen, readCatalog } from './catalog.js';
10
+ import { isUnfilled } from './companion.js';
10
11
  import { CONFIG_PATH, DEFAULT_LAYOUT, KINDS, OVERRIDES_DIR, readConfig } from './config.js';
11
12
  import { isRefusal } from './plan.js';
12
13
  import { pendingOf, planSync, runSync } from './sync.js';
@@ -34,16 +35,28 @@ const NOT_CHOSEN = 'не выбран';
34
35
  const SKIPPED = 'пропущен';
35
36
  const KIND_TITLE = {
36
37
  laws: 'ЗАКОНЫ',
38
+ rules: 'ПРАВИЛА',
39
+ patterns: 'ПАТТЕРНЫ',
37
40
  hooks: 'ХУКИ',
38
41
  checks: 'ПРОВЕРКИ',
39
42
  agents: 'АГЕНТЫ',
43
+ commands: 'КОМАНДЫ',
44
+ workflows: 'ВОРКФЛОУ',
40
45
  templates: 'ШАБЛОНЫ',
41
46
  };
47
+ /** Состояние компаньона словами. У заполненного слова нет: о нём говорить нечего. */
48
+ const COMPANION_WORD = {
49
+ missing: 'пропал',
50
+ draft: 'остался черновиком',
51
+ filled: 'заполнен',
52
+ };
42
53
  const NO_CONFIG = `нет \`${CONFIG_PATH}\` — начни с \`agent-kit init\``;
43
54
  const holes = (result) => [...result.missing].map(([asset, names]) => ` ${asset}: нет значений для ${names.map((name) => `{{${name}}}`).join(', ')}`);
55
+ const unfilled = (result) => result.companions.filter(isUnfilled);
44
56
  const describe = (result) => [
45
57
  ...holes(result),
46
58
  ...pendingOf(result).map((entry) => ` ${entry.path} — ${STATE_WORD[entry.outcome]}`),
59
+ ...unfilled(result).map((entry) => ` ${entry.path} — ${COMPANION_WORD[entry.state]}`),
47
60
  ];
48
61
  /**
49
62
  * Что проект выбрал при заведении конфига. Пустой список — весь набор; выбор приходит сюда
@@ -78,10 +91,16 @@ export function sync(env, check) {
78
91
  if (check) {
79
92
  const result = planSync(config, root, version, assetsDir);
80
93
  const pending = pendingOf(result);
81
- if (!result.missing.size && !pending.length) {
94
+ // Незаполненный компаньон такое же расхождение, как отставший файл: правило разложено,
95
+ // а имён этого дерева при нём нет, и агент читает указание, которому некуда примениться.
96
+ const empty = unfilled(result);
97
+ if (!result.missing.size && !pending.length && !empty.length) {
82
98
  return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`] };
83
99
  }
84
- return { code: 1, lines: [`sync --check: расхождений ${result.missing.size + pending.length}`, ...describe(result)] };
100
+ return {
101
+ code: 1,
102
+ lines: [`sync --check: расхождений ${result.missing.size + pending.length + empty.length}`, ...describe(result)],
103
+ };
85
104
  }
86
105
  const result = runSync(config, root, version, assetsDir);
87
106
  // Перечисляются только дырки: раскладка не начата, и назвать файл «положенным» значило бы
@@ -1 +1 @@
1
- {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Команды и их вывод. Печать отделена от работы: команда возвращает строки и код возврата,\n * а `process.exit` зовёт только точка входа — иначе ни одну из них нельзя было бы проверить\n * спекой, не перехватывая поток вывода.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { collectAssets } from './assets.js';\nimport { IEntryOfCatalog, isChosen, readCatalog } from './catalog.js';\nimport { CONFIG_PATH, DEFAULT_LAYOUT, IConfig, KINDS, OVERRIDES_DIR, readConfig, TKind } from './config.js';\nimport { IPlanned, isRefusal, TOutcome } from './plan.js';\nimport { ISyncResult, pendingOf, planSync, runSync } from './sync.js';\n\nexport interface IOutcomeOfCommand {\n readonly code: number;\n readonly lines: readonly string[];\n}\n\n/** Всё, что команда знает о мире: корень проекта, версия пакета и его каталог ресурсов. */\nexport interface IEnvironment {\n readonly root: string;\n readonly version: string;\n readonly assetsDir: string;\n}\n\n/** Что с файлом сделала раскладка. Прошедшее время здесь правда: `sync` уже записал. */\nconst DONE_WORD: Readonly<Record<TOutcome, string>> = {\n create: 'положен',\n update: 'переложен',\n drift: 'правлен руками',\n foreign: 'положен не пакетом',\n ok: 'без изменений',\n};\n\n/**\n * Что с файлом **сейчас**. Отдельно от прошедшего времени потому, что `doctor` и `list` ничего\n * не пишут: сказать «положен» о файле, которого в дереве нет, значит соврать читателю ровно там,\n * где он пришёл узнать состояние.\n */\nconst STATE_WORD: Readonly<Record<TOutcome, string>> = {\n create: 'нет в дереве',\n update: 'отстал от пакета',\n drift: 'правлен руками',\n foreign: 'положен не пакетом',\n ok: 'на месте',\n};\n\nconst NOT_CHOSEN: string = 'не выбран';\nconst SKIPPED: string = 'пропущен';\n\nconst KIND_TITLE: Readonly<Record<TKind, string>> = {\n laws: 'ЗАКОНЫ',\n hooks: 'ХУКИ',\n checks: 'ПРОВЕРКИ',\n agents: 'АГЕНТЫ',\n templates: 'ШАБЛОНЫ',\n};\n\nconst NO_CONFIG: string = `нет \\`${CONFIG_PATH}\\` — начни с \\`agent-kit init\\``;\n\nconst holes: (result: ISyncResult) => string[] = (result: ISyncResult): string[] =>\n [...result.missing].map(\n ([asset, names]: [string, readonly string[]]): string =>\n ` ${asset}: нет значений для ${names.map((name: string): string => `{{${name}}}`).join(', ')}`\n );\n\nconst describe: (result: ISyncResult) => string[] = (result: ISyncResult): string[] => [\n ...holes(result),\n ...pendingOf(result).map((entry: IPlanned): string => ` ${entry.path} — ${STATE_WORD[entry.outcome]}`),\n];\n\n/**\n * Что проект выбрал при заведении конфига. Пустой список — весь набор; выбор приходит сюда\n * готовым, потому что взять его негде, кроме края: у строки запуска флаг, у терминала вопрос,\n * а у неинтерактивного прогона нет ни того ни другого — и решать это команде не по чину.\n */\nexport function init(root: string, only: readonly string[] = []): IOutcomeOfCommand {\n const path: string = join(root, CONFIG_PATH);\n if (existsSync(path)) {\n return { code: 0, lines: [`${CONFIG_PATH} уже есть — оставлен как есть`] };\n }\n\n const config: object = { vars: {}, layout: DEFAULT_LAYOUT, only, skip: [] };\n mkdirSync(join(root, OVERRIDES_DIR), { recursive: true });\n mkdirSync(join(root, CONFIG_PATH, '..'), { recursive: true });\n writeFileSync(path, `${JSON.stringify(config, null, 4)}\\n`, 'utf8');\n\n return {\n code: 0,\n lines: [\n `заведён ${CONFIG_PATH}`,\n only.length ? `выбрано ресурсов: ${only.length}` : 'выбрано всё, что везёт пакет',\n `заведён ${OVERRIDES_DIR}/ — надстройки проекта кладутся сюда путём ресурса`,\n 'дальше: заполни `vars` и запусти `agent-kit sync`',\n ],\n };\n}\n\nexport function sync(env: IEnvironment, check: boolean): IOutcomeOfCommand {\n const { root, version, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n if (!config) {\n return { code: 1, lines: [NO_CONFIG] };\n }\n\n if (check) {\n const result: ISyncResult = planSync(config, root, version, assetsDir);\n const pending: readonly IPlanned[] = pendingOf(result);\n if (!result.missing.size && !pending.length) {\n return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`] };\n }\n\n return { code: 1, lines: [`sync --check: расхождений ${result.missing.size + pending.length}`, ...describe(result)] };\n }\n\n const result: ISyncResult = runSync(config, root, version, assetsDir);\n // Перечисляются только дырки: раскладка не начата, и назвать файл «положенным» значило бы\n // сказать неправду — на диске его нет.\n if (result.missing.size) {\n return { code: 1, lines: ['раскладка не начата: нечего подставить в дырки', ...holes(result)] };\n }\n\n const refused: readonly IPlanned[] = result.planned.filter((entry: IPlanned): boolean => isRefusal(entry.outcome));\n if (refused.length) {\n return {\n code: 1,\n lines: [\n 'раскладка не начата: эти файлы пакет переписывать не станет',\n ...refused.map((entry: IPlanned): string => ` ${entry.path} — ${DONE_WORD[entry.outcome]}`),\n 'правку надо либо перенести в надстройку, либо снять — и повторить',\n ],\n };\n }\n\n return {\n code: 0,\n lines: result.written.length\n ? [`разложено файлов: ${result.written.length}`, ...result.written.map((path: string): string => ` ${path}`)]\n : ['всё уже разложено'],\n };\n}\n\nexport function doctor(env: IEnvironment): IOutcomeOfCommand {\n const { root, version, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n if (!config) {\n return { code: 1, lines: [NO_CONFIG] };\n }\n\n const result: ISyncResult = planSync(config, root, version, assetsDir);\n const counted: Map<TOutcome, number> = new Map();\n for (const entry of result.planned) {\n counted.set(entry.outcome, (counted.get(entry.outcome) ?? 0) + 1);\n }\n\n const catalog: readonly IEntryOfCatalog[] = readCatalog(assetsDir);\n const taken: number = collectAssets(config, assetsDir).length;\n\n const lines: string[] = [\n `пакет v${version}, везёт ресурсов ${catalog.length}, взято ${taken}`,\n `не выбрано: ${catalog.length - taken - config.skip.length}, пропущено: ${config.skip.length}`,\n `значений в конфиге: ${Object.keys(config.vars).length}`,\n ...[...counted].map(([outcome, count]: [TOutcome, number]): string => `${STATE_WORD[outcome]}: ${count}`),\n ...describe(result),\n ];\n\n return { code: 0, lines };\n}\n\n/**\n * Что пакет везёт и что из этого взято здесь.\n *\n * Работает и без конфига: посмотреть, из чего выбирать, нужно раньше, чем выбор сделан, — иначе\n * имена законов взять неоткуда, кроме исходников пакета.\n */\nexport function list(env: IEnvironment): IOutcomeOfCommand {\n const { root, version, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n const catalog: readonly IEntryOfCatalog[] = readCatalog(assetsDir);\n const planned: Map<string, TOutcome> = new Map();\n\n if (config) {\n for (const entry of planSync(config, root, version, assetsDir).planned) {\n planned.set(entry.asset, entry.outcome);\n }\n }\n\n const stateOf: (entry: IEntryOfCatalog) => string = (entry: IEntryOfCatalog): string => {\n if (!config) {\n return 'везёт пакет';\n }\n if (config.skip.includes(entry.id)) {\n return SKIPPED;\n }\n if (!isChosen(entry, config.only, config.skip)) {\n return NOT_CHOSEN;\n }\n const outcome: TOutcome | undefined = planned.get(entry.id);\n\n // Ресурса нет среди запланированных ровно в одном случае: в нём осталась дырка без\n // значения, и до решения о судьбе файла раскладка не дошла.\n return outcome ? STATE_WORD[outcome] : 'дырка без значения';\n };\n\n const name: number = catalog.reduce((found: number, entry: IEntryOfCatalog): number => Math.max(found, entry.name.length), 0);\n const state: number = catalog.reduce((found: number, entry: IEntryOfCatalog): number => Math.max(found, stateOf(entry).length), 0);\n const lines: string[] = [config ? `пакет v${version}` : `пакет v${version}, ${CONFIG_PATH} ещё не заведён`];\n\n for (const kind of KINDS) {\n const entries: readonly IEntryOfCatalog[] = catalog.filter((entry: IEntryOfCatalog): boolean => entry.kind === kind);\n if (!entries.length) {\n continue;\n }\n lines.push('', KIND_TITLE[kind]);\n lines.push(\n ...entries.map(\n (entry: IEntryOfCatalog): string => ` ${entry.name.padEnd(name)} ${stateOf(entry).padEnd(state)} ${entry.title}`\n )\n );\n }\n\n return { code: 0, lines };\n}\n"]}
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Команды и их вывод. Печать отделена от работы: команда возвращает строки и код возврата,\n * а `process.exit` зовёт только точка входа — иначе ни одну из них нельзя было бы проверить\n * спекой, не перехватывая поток вывода.\n */\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { collectAssets } from './assets.js';\nimport { IEntryOfCatalog, isChosen, readCatalog } from './catalog.js';\nimport { ICompanion, isUnfilled, TCompanionState } from './companion.js';\nimport { CONFIG_PATH, DEFAULT_LAYOUT, IConfig, KINDS, OVERRIDES_DIR, readConfig, TKind } from './config.js';\nimport { IPlanned, isRefusal, TOutcome } from './plan.js';\nimport { ISyncResult, pendingOf, planSync, runSync } from './sync.js';\n\nexport interface IOutcomeOfCommand {\n readonly code: number;\n readonly lines: readonly string[];\n}\n\n/** Всё, что команда знает о мире: корень проекта, версия пакета и его каталог ресурсов. */\nexport interface IEnvironment {\n readonly root: string;\n readonly version: string;\n readonly assetsDir: string;\n}\n\n/** Что с файлом сделала раскладка. Прошедшее время здесь правда: `sync` уже записал. */\nconst DONE_WORD: Readonly<Record<TOutcome, string>> = {\n create: 'положен',\n update: 'переложен',\n drift: 'правлен руками',\n foreign: 'положен не пакетом',\n ok: 'без изменений',\n};\n\n/**\n * Что с файлом **сейчас**. Отдельно от прошедшего времени потому, что `doctor` и `list` ничего\n * не пишут: сказать «положен» о файле, которого в дереве нет, значит соврать читателю ровно там,\n * где он пришёл узнать состояние.\n */\nconst STATE_WORD: Readonly<Record<TOutcome, string>> = {\n create: 'нет в дереве',\n update: 'отстал от пакета',\n drift: 'правлен руками',\n foreign: 'положен не пакетом',\n ok: 'на месте',\n};\n\nconst NOT_CHOSEN: string = 'не выбран';\nconst SKIPPED: string = 'пропущен';\n\nconst KIND_TITLE: Readonly<Record<TKind, string>> = {\n laws: 'ЗАКОНЫ',\n rules: 'ПРАВИЛА',\n patterns: 'ПАТТЕРНЫ',\n hooks: 'ХУКИ',\n checks: 'ПРОВЕРКИ',\n agents: 'АГЕНТЫ',\n commands: 'КОМАНДЫ',\n workflows: 'ВОРКФЛОУ',\n templates: 'ШАБЛОНЫ',\n};\n\n/** Состояние компаньона словами. У заполненного слова нет: о нём говорить нечего. */\nconst COMPANION_WORD: Readonly<Record<TCompanionState, string>> = {\n missing: 'пропал',\n draft: 'остался черновиком',\n filled: 'заполнен',\n};\n\nconst NO_CONFIG: string = `нет \\`${CONFIG_PATH}\\` — начни с \\`agent-kit init\\``;\n\nconst holes: (result: ISyncResult) => string[] = (result: ISyncResult): string[] =>\n [...result.missing].map(\n ([asset, names]: [string, readonly string[]]): string =>\n ` ${asset}: нет значений для ${names.map((name: string): string => `{{${name}}}`).join(', ')}`\n );\n\nconst unfilled: (result: ISyncResult) => readonly ICompanion[] = (result: ISyncResult): readonly ICompanion[] =>\n result.companions.filter(isUnfilled);\n\nconst describe: (result: ISyncResult) => string[] = (result: ISyncResult): string[] => [\n ...holes(result),\n ...pendingOf(result).map((entry: IPlanned): string => ` ${entry.path} — ${STATE_WORD[entry.outcome]}`),\n ...unfilled(result).map((entry: ICompanion): string => ` ${entry.path} — ${COMPANION_WORD[entry.state]}`),\n];\n\n/**\n * Что проект выбрал при заведении конфига. Пустой список — весь набор; выбор приходит сюда\n * готовым, потому что взять его негде, кроме края: у строки запуска флаг, у терминала вопрос,\n * а у неинтерактивного прогона нет ни того ни другого — и решать это команде не по чину.\n */\nexport function init(root: string, only: readonly string[] = []): IOutcomeOfCommand {\n const path: string = join(root, CONFIG_PATH);\n if (existsSync(path)) {\n return { code: 0, lines: [`${CONFIG_PATH} уже есть — оставлен как есть`] };\n }\n\n const config: object = { vars: {}, layout: DEFAULT_LAYOUT, only, skip: [] };\n mkdirSync(join(root, OVERRIDES_DIR), { recursive: true });\n mkdirSync(join(root, CONFIG_PATH, '..'), { recursive: true });\n writeFileSync(path, `${JSON.stringify(config, null, 4)}\\n`, 'utf8');\n\n return {\n code: 0,\n lines: [\n `заведён ${CONFIG_PATH}`,\n only.length ? `выбрано ресурсов: ${only.length}` : 'выбрано всё, что везёт пакет',\n `заведён ${OVERRIDES_DIR}/ — надстройки проекта кладутся сюда путём ресурса`,\n 'дальше: заполни `vars` и запусти `agent-kit sync`',\n ],\n };\n}\n\nexport function sync(env: IEnvironment, check: boolean): IOutcomeOfCommand {\n const { root, version, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n if (!config) {\n return { code: 1, lines: [NO_CONFIG] };\n }\n\n if (check) {\n const result: ISyncResult = planSync(config, root, version, assetsDir);\n const pending: readonly IPlanned[] = pendingOf(result);\n // Незаполненный компаньон — такое же расхождение, как отставший файл: правило разложено,\n // а имён этого дерева при нём нет, и агент читает указание, которому некуда примениться.\n const empty: readonly ICompanion[] = unfilled(result);\n if (!result.missing.size && !pending.length && !empty.length) {\n return { code: 0, lines: [`sync --check: разложенное сходится с пакетом v${version}`] };\n }\n\n return {\n code: 1,\n lines: [`sync --check: расхождений ${result.missing.size + pending.length + empty.length}`, ...describe(result)],\n };\n }\n\n const result: ISyncResult = runSync(config, root, version, assetsDir);\n // Перечисляются только дырки: раскладка не начата, и назвать файл «положенным» значило бы\n // сказать неправду — на диске его нет.\n if (result.missing.size) {\n return { code: 1, lines: ['раскладка не начата: нечего подставить в дырки', ...holes(result)] };\n }\n\n const refused: readonly IPlanned[] = result.planned.filter((entry: IPlanned): boolean => isRefusal(entry.outcome));\n if (refused.length) {\n return {\n code: 1,\n lines: [\n 'раскладка не начата: эти файлы пакет переписывать не станет',\n ...refused.map((entry: IPlanned): string => ` ${entry.path} — ${DONE_WORD[entry.outcome]}`),\n 'правку надо либо перенести в надстройку, либо снять — и повторить',\n ],\n };\n }\n\n return {\n code: 0,\n lines: result.written.length\n ? [`разложено файлов: ${result.written.length}`, ...result.written.map((path: string): string => ` ${path}`)]\n : ['всё уже разложено'],\n };\n}\n\nexport function doctor(env: IEnvironment): IOutcomeOfCommand {\n const { root, version, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n if (!config) {\n return { code: 1, lines: [NO_CONFIG] };\n }\n\n const result: ISyncResult = planSync(config, root, version, assetsDir);\n const counted: Map<TOutcome, number> = new Map();\n for (const entry of result.planned) {\n counted.set(entry.outcome, (counted.get(entry.outcome) ?? 0) + 1);\n }\n\n const catalog: readonly IEntryOfCatalog[] = readCatalog(assetsDir);\n const taken: number = collectAssets(config, assetsDir).length;\n\n const lines: string[] = [\n `пакет v${version}, везёт ресурсов ${catalog.length}, взято ${taken}`,\n `не выбрано: ${catalog.length - taken - config.skip.length}, пропущено: ${config.skip.length}`,\n `значений в конфиге: ${Object.keys(config.vars).length}`,\n ...[...counted].map(([outcome, count]: [TOutcome, number]): string => `${STATE_WORD[outcome]}: ${count}`),\n ...describe(result),\n ];\n\n return { code: 0, lines };\n}\n\n/**\n * Что пакет везёт и что из этого взято здесь.\n *\n * Работает и без конфига: посмотреть, из чего выбирать, нужно раньше, чем выбор сделан, — иначе\n * имена законов взять неоткуда, кроме исходников пакета.\n */\nexport function list(env: IEnvironment): IOutcomeOfCommand {\n const { root, version, assetsDir } = env;\n const config: IConfig | null = readConfig(root);\n const catalog: readonly IEntryOfCatalog[] = readCatalog(assetsDir);\n const planned: Map<string, TOutcome> = new Map();\n\n if (config) {\n for (const entry of planSync(config, root, version, assetsDir).planned) {\n planned.set(entry.asset, entry.outcome);\n }\n }\n\n const stateOf: (entry: IEntryOfCatalog) => string = (entry: IEntryOfCatalog): string => {\n if (!config) {\n return 'везёт пакет';\n }\n if (config.skip.includes(entry.id)) {\n return SKIPPED;\n }\n if (!isChosen(entry, config.only, config.skip)) {\n return NOT_CHOSEN;\n }\n const outcome: TOutcome | undefined = planned.get(entry.id);\n\n // Ресурса нет среди запланированных ровно в одном случае: в нём осталась дырка без\n // значения, и до решения о судьбе файла раскладка не дошла.\n return outcome ? STATE_WORD[outcome] : 'дырка без значения';\n };\n\n const name: number = catalog.reduce((found: number, entry: IEntryOfCatalog): number => Math.max(found, entry.name.length), 0);\n const state: number = catalog.reduce((found: number, entry: IEntryOfCatalog): number => Math.max(found, stateOf(entry).length), 0);\n const lines: string[] = [config ? `пакет v${version}` : `пакет v${version}, ${CONFIG_PATH} ещё не заведён`];\n\n for (const kind of KINDS) {\n const entries: readonly IEntryOfCatalog[] = catalog.filter((entry: IEntryOfCatalog): boolean => entry.kind === kind);\n if (!entries.length) {\n continue;\n }\n lines.push('', KIND_TITLE[kind]);\n lines.push(\n ...entries.map(\n (entry: IEntryOfCatalog): string => ` ${entry.name.padEnd(name)} ${stateOf(entry).padEnd(state)} ${entry.title}`\n )\n );\n }\n\n return { code: 0, lines };\n}\n"]}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.
3
+ *
4
+ * Правило пакета переносимо ровно потому, что не знает ни путей, ни имён: оно говорит, какой
5
+ * приём здесь действует и что сломается без него. Но правило без имён дерева — это закон, и
6
+ * агент по нему работать не может: ему нужно знать, как называется здесь то, о чём идёт речь,
7
+ * и где оно лежит.
8
+ *
9
+ * Поэтому имена живут рядом с правилом, в отдельном файле, и пишет его проект. Пакет кладёт
10
+ * черновик один раз и больше к нему не возвращается: сверять его нечем — своего текста у пакета
11
+ * там нет, а перекладывать значило бы стирать написанное проектом.
12
+ *
13
+ * Отсюда и три состояния вместо двух. Черновик, который никто не заполнил, — не то же самое,
14
+ * что заполненный компаньон, и не то же самое, что пропавший: он выглядит рабочим файлом, но
15
+ * правило при нём остаётся указанием без адресата.
16
+ */
17
+ import { IAsset } from './assets.js';
18
+ /**
19
+ * Метка незаполненного места. Стоит в каждой строке черновика, которую должен заменить проект,
20
+ * и снимается вместе с ней: пока метка на месте, компаньон не заполнен.
21
+ */
22
+ export declare const COMPANION_MARK: string;
23
+ export type TCompanionState =
24
+ /** Файла нет: правило разложено, а имён дерева при нём не оказалось. */
25
+ 'missing'
26
+ /** Черновик лежит, но остался как положен. */
27
+ | 'draft'
28
+ /** Проект его заполнил. */
29
+ | 'filled';
30
+ export interface ICompanion {
31
+ /** Правило, при котором стоит компаньон. */
32
+ readonly rule: string;
33
+ readonly path: string;
34
+ readonly state: TCompanionState;
35
+ /** Что записать; `null` — писать нечего, файл уже лежит. */
36
+ readonly content: string | null;
37
+ }
38
+ /** Путь компаньона: рядом с самим правилом, в каталоге его имени. */
39
+ export declare const pathOf: (asset: IAsset) => string;
40
+ /**
41
+ * Место имени правила в шаблоне. Угловые скобки, а не `{{дырка}}`: дырка без значения отказывает
42
+ * раскладке, а шаблон компаньона пакет кладёт проекту и сам — на нём раскладка и споткнулась бы.
43
+ */
44
+ export declare const RULE_SLOT: string;
45
+ /**
46
+ * Черновик по шаблону пакета. Имя правила подставляется, чтобы заполняющий не гадал, к чему
47
+ * файл относится, — открывают его обычно из гейта, а не из правила.
48
+ */
49
+ export declare const draftOf: (template: string, rule: string) => string;
50
+ export declare function planCompanion(asset: IAsset, existing: string | null, template: string): ICompanion;
51
+ /** Что `sync --check` считает недоделанным: правило при пустом компаньоне не действует. */
52
+ export declare const isUnfilled: (companion: ICompanion) => boolean;
53
+ //# sourceMappingURL=companion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"companion.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/companion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,MAAoC,CAAC;AAElE,MAAM,MAAM,eAAe;AACvB,wEAAwE;AACtE,SAAS;AACX,8CAA8C;GAC5C,OAAO;AACT,2BAA2B;GACzB,QAAQ,CAAC;AAEf,MAAM,WAAW,UAAU;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,qEAAqE;AACrE,eAAO,MAAM,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAkF,CAAC;AAE3H;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,MAAwB,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAClB,CAAC;AAEzC,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAYlG;AAED,2FAA2F;AAC3F,eAAO,MAAM,UAAU,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAA0E,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { COMPANION_FILE } from './config.js';
2
+ /**
3
+ * Метка незаполненного места. Стоит в каждой строке черновика, которую должен заменить проект,
4
+ * и снимается вместе с ней: пока метка на месте, компаньон не заполнен.
5
+ */
6
+ export const COMPANION_MARK = '<!-- заполняет проект -->';
7
+ /** Путь компаньона: рядом с самим правилом, в каталоге его имени. */
8
+ export const pathOf = (asset) => asset.target.replace(/[^/]+$/, COMPANION_FILE);
9
+ /**
10
+ * Место имени правила в шаблоне. Угловые скобки, а не `{{дырка}}`: дырка без значения отказывает
11
+ * раскладке, а шаблон компаньона пакет кладёт проекту и сам — на нём раскладка и споткнулась бы.
12
+ */
13
+ export const RULE_SLOT = '<имя-правила>';
14
+ /**
15
+ * Черновик по шаблону пакета. Имя правила подставляется, чтобы заполняющий не гадал, к чему
16
+ * файл относится, — открывают его обычно из гейта, а не из правила.
17
+ */
18
+ export const draftOf = (template, rule) => template.split(RULE_SLOT).join(rule);
19
+ export function planCompanion(asset, existing, template) {
20
+ const path = pathOf(asset);
21
+ if (existing === null) {
22
+ return { rule: asset.name, path, state: 'missing', content: draftOf(template, asset.name) };
23
+ }
24
+ return {
25
+ rule: asset.name,
26
+ path,
27
+ state: existing.includes(COMPANION_MARK) ? 'draft' : 'filled',
28
+ content: null,
29
+ };
30
+ }
31
+ /** Что `sync --check` считает недоделанным: правило при пустом компаньоне не действует. */
32
+ export const isUnfilled = (companion) => companion.state !== 'filled';
33
+ //# sourceMappingURL=companion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"companion.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/companion.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAW,2BAA2B,CAAC;AAmBlE,qEAAqE;AACrE,MAAM,CAAC,MAAM,MAAM,GAA8B,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AAE3H;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAW,eAAe,CAAC;AAEjD;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAA+C,CAAC,QAAgB,EAAE,IAAY,EAAU,EAAE,CAC1G,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEzC,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,QAAuB,EAAE,QAAgB;IAClF,MAAM,IAAI,GAAW,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IAChG,CAAC;IAED,OAAO;QACH,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI;QACJ,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;QAC7D,OAAO,EAAE,IAAI;KAChB,CAAC;AACN,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,MAAM,UAAU,GAAuC,CAAC,SAAqB,EAAW,EAAE,CAAC,SAAS,CAAC,KAAK,KAAK,QAAQ,CAAC","sourcesContent":["/**\n * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.\n *\n * Правило пакета переносимо ровно потому, что не знает ни путей, ни имён: оно говорит, какой\n * приём здесь действует и что сломается без него. Но правило без имён дерева — это закон, и\n * агент по нему работать не может: ему нужно знать, как называется здесь то, о чём идёт речь,\n * и где оно лежит.\n *\n * Поэтому имена живут рядом с правилом, в отдельном файле, и пишет его проект. Пакет кладёт\n * черновик один раз и больше к нему не возвращается: сверять его нечем — своего текста у пакета\n * там нет, а перекладывать значило бы стирать написанное проектом.\n *\n * Отсюда и три состояния вместо двух. Черновик, который никто не заполнил, — не то же самое,\n * что заполненный компаньон, и не то же самое, что пропавший: он выглядит рабочим файлом, но\n * правило при нём остаётся указанием без адресата.\n */\nimport { IAsset } from './assets.js';\nimport { COMPANION_FILE } from './config.js';\n\n/**\n * Метка незаполненного места. Стоит в каждой строке черновика, которую должен заменить проект,\n * и снимается вместе с ней: пока метка на месте, компаньон не заполнен.\n */\nexport const COMPANION_MARK: string = '<!-- заполняет проект -->';\n\nexport type TCompanionState =\n /** Файла нет: правило разложено, а имён дерева при нём не оказалось. */\n | 'missing'\n /** Черновик лежит, но остался как положен. */\n | 'draft'\n /** Проект его заполнил. */\n | 'filled';\n\nexport interface ICompanion {\n /** Правило, при котором стоит компаньон. */\n readonly rule: string;\n readonly path: string;\n readonly state: TCompanionState;\n /** Что записать; `null` — писать нечего, файл уже лежит. */\n readonly content: string | null;\n}\n\n/** Путь компаньона: рядом с самим правилом, в каталоге его имени. */\nexport const pathOf: (asset: IAsset) => string = (asset: IAsset): string => asset.target.replace(/[^/]+$/, COMPANION_FILE);\n\n/**\n * Место имени правила в шаблоне. Угловые скобки, а не `{{дырка}}`: дырка без значения отказывает\n * раскладке, а шаблон компаньона пакет кладёт проекту и сам — на нём раскладка и споткнулась бы.\n */\nexport const RULE_SLOT: string = '<имя-правила>';\n\n/**\n * Черновик по шаблону пакета. Имя правила подставляется, чтобы заполняющий не гадал, к чему\n * файл относится, — открывают его обычно из гейта, а не из правила.\n */\nexport const draftOf: (template: string, rule: string) => string = (template: string, rule: string): string =>\n template.split(RULE_SLOT).join(rule);\n\nexport function planCompanion(asset: IAsset, existing: string | null, template: string): ICompanion {\n const path: string = pathOf(asset);\n if (existing === null) {\n return { rule: asset.name, path, state: 'missing', content: draftOf(template, asset.name) };\n }\n\n return {\n rule: asset.name,\n path,\n state: existing.includes(COMPANION_MARK) ? 'draft' : 'filled',\n content: null,\n };\n}\n\n/** Что `sync --check` считает недоделанным: правило при пустом компаньоне не действует. */\nexport const isUnfilled: (companion: ICompanion) => boolean = (companion: ICompanion): boolean => companion.state !== 'filled';\n"]}
package/lib/config.d.ts CHANGED
@@ -1,6 +1,20 @@
1
1
  /** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */
2
- export type TKind = 'laws' | 'hooks' | 'checks' | 'agents' | 'templates';
2
+ export type TKind = 'laws' | 'rules' | 'patterns' | 'hooks' | 'checks' | 'agents' | 'commands' | 'workflows' | 'templates';
3
3
  export declare const KINDS: readonly TKind[];
4
+ /**
5
+ * Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как
6
+ * `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.
7
+ */
8
+ export declare const SKILL_KINDS: readonly TKind[];
9
+ /** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */
10
+ export declare const SKILL_FILE: string;
11
+ /**
12
+ * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.
13
+ *
14
+ * Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один
15
+ * раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.
16
+ */
17
+ export declare const COMPANION_FILE: string;
4
18
  export interface IConfig {
5
19
  /** Значения дырок `{{имя}}`. */
6
20
  readonly vars: Readonly<Record<string, string>>;
@@ -16,6 +30,15 @@ export interface IConfig {
16
30
  }
17
31
  export declare const CONFIG_PATH: string;
18
32
  export declare const OVERRIDES_DIR: string;
33
+ /** Имя дырки под путь карты гейта и её умолчание: карту пишет проект, путь знают оба. */
34
+ export declare const GATE_MAP_VAR: string;
35
+ export declare const DEFAULT_GATE_MAP: string;
36
+ /**
37
+ * Профиль проекта: команды, стенды и пары «правка — документ» этого дерева. Его читают
38
+ * сторожевые хуки — механизм у них общий, а всё, что они зовут и называют, своё у каждого.
39
+ */
40
+ export declare const PROFILE_VAR: string;
41
+ export declare const DEFAULT_PROFILE: string;
19
42
  /**
20
43
  * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:
21
44
  * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"AAUA,2EAA2E;AAC3E,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEzE,eAAO,MAAM,KAAK,EAAE,SAAS,KAAK,EAAuD,CAAC;AAE1F,MAAM,WAAW,OAAO;IACpB,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,eAAO,MAAM,WAAW,EAAE,MAA8B,CAAC;AACzD,eAAO,MAAM,aAAa,EAAE,MAAmC,CAAC;AAEhE;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAM1D,CAAC;AAEF,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAgCzC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA6BjD;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAUvD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"AAUA,2EAA2E;AAC3E,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;AAE3H,eAAO,MAAM,KAAK,EAAE,SAAS,KAAK,EAAqG,CAAC;AAExI;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,KAAK,EAA0B,CAAC;AAEnE,iFAAiF;AACjF,eAAO,MAAM,UAAU,EAAE,MAAmB,CAAC;AAE7C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,MAA4B,CAAC;AAE1D,MAAM,WAAW,OAAO;IACpB,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,eAAO,MAAM,WAAW,EAAE,MAA8B,CAAC;AACzD,eAAO,MAAM,aAAa,EAAE,MAAmC,CAAC;AAEhE,yFAAyF;AACzF,eAAO,MAAM,YAAY,EAAE,MAAkB,CAAC;AAC9C,eAAO,MAAM,gBAAgB,EAAE,MAAqC,CAAC;AAErE;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,MAAyB,CAAC;AACpD,eAAO,MAAM,eAAe,EAAE,MAAoC,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAU1D,CAAC;AAEF,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAgCzC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkCjD;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAUvD"}
package/lib/config.js CHANGED
@@ -7,9 +7,32 @@
7
7
  */
8
8
  import { readFileSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
- export const KINDS = ['laws', 'hooks', 'checks', 'agents', 'templates'];
10
+ export const KINDS = ['laws', 'rules', 'patterns', 'hooks', 'checks', 'agents', 'commands', 'workflows', 'templates'];
11
+ /**
12
+ * Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как
13
+ * `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.
14
+ */
15
+ export const SKILL_KINDS = ['rules', 'patterns'];
16
+ /** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */
17
+ export const SKILL_FILE = 'SKILL.md';
18
+ /**
19
+ * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.
20
+ *
21
+ * Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один
22
+ * раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.
23
+ */
24
+ export const COMPANION_FILE = 'implementation.md';
11
25
  export const CONFIG_PATH = '.claude/rt-kit.json';
12
26
  export const OVERRIDES_DIR = '.claude/rt-kit/overrides';
27
+ /** Имя дырки под путь карты гейта и её умолчание: карту пишет проект, путь знают оба. */
28
+ export const GATE_MAP_VAR = 'gateMap';
29
+ export const DEFAULT_GATE_MAP = '.claude/rt-kit/gate-map.sh';
30
+ /**
31
+ * Профиль проекта: команды, стенды и пары «правка — документ» этого дерева. Его читают
32
+ * сторожевые хуки — механизм у них общий, а всё, что они зовут и называют, своё у каждого.
33
+ */
34
+ export const PROFILE_VAR = 'projectProfile';
35
+ export const DEFAULT_PROFILE = '.claude/rt-kit/project.sh';
13
36
  /**
14
37
  * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:
15
38
  * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет
@@ -17,9 +40,13 @@ export const OVERRIDES_DIR = '.claude/rt-kit/overrides';
17
40
  */
18
41
  export const DEFAULT_LAYOUT = {
19
42
  laws: 'docs/constitution',
43
+ rules: '.claude/skills',
44
+ patterns: '.claude/skills',
20
45
  hooks: '.claude/hooks',
21
46
  checks: 'tools',
22
47
  agents: '.claude/agents',
48
+ commands: '.claude/commands',
49
+ workflows: '.claude/workflows',
23
50
  templates: '.claude/rt-kit/templates',
24
51
  };
25
52
  export class ConfigError extends Error {
@@ -73,6 +100,11 @@ export function parseConfig(text) {
73
100
  for (const kind of KINDS) {
74
101
  derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];
75
102
  }
103
+ // Карту гейта пишет проект, но путь к ней знают обе стороны: хук её читает, шаблон
104
+ // ложится рядом. Умолчание здесь избавляет проект от обязанности объявлять его самому —
105
+ // а переопределить его он всё равно может, значением в `vars`.
106
+ derived[GATE_MAP_VAR] = DEFAULT_GATE_MAP;
107
+ derived[PROFILE_VAR] = DEFAULT_PROFILE;
76
108
  return { vars: { ...derived, ...stringMap(raw['vars'], 'vars') }, layout: layout, only, skip };
77
109
  }
78
110
  /** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */