@rt-tools/agent-kit 0.1.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 (64) hide show
  1. package/README.md +85 -0
  2. package/assets/laws/access.md +46 -0
  3. package/assets/laws/admin-lists.md +47 -0
  4. package/assets/laws/admin-navigation.md +50 -0
  5. package/assets/laws/code-structure.md +32 -0
  6. package/assets/laws/delivery.md +34 -0
  7. package/assets/laws/entity-editing.md +73 -0
  8. package/assets/laws/entity-models.md +34 -0
  9. package/assets/laws/frontend-application.md +34 -0
  10. package/assets/laws/lib-imports.md +31 -0
  11. package/assets/laws/locales.md +44 -0
  12. package/assets/laws/project-documentation.md +39 -0
  13. package/assets/laws/reuse-first.md +35 -0
  14. package/assets/laws/search-visibility.md +37 -0
  15. package/assets/laws/shared-code.md +32 -0
  16. package/assets/laws/verifiability.md +33 -0
  17. package/assets/templates/pattern.md +29 -0
  18. package/assets/templates/rule.md +43 -0
  19. package/bin/agent-kit.d.ts +4 -0
  20. package/bin/agent-kit.d.ts.map +1 -0
  21. package/bin/agent-kit.js +56 -0
  22. package/bin/agent-kit.js.map +1 -0
  23. package/index.d.ts +9 -0
  24. package/index.d.ts.map +1 -0
  25. package/index.js +9 -0
  26. package/index.js.map +1 -0
  27. package/lib/assets.d.ts +11 -0
  28. package/lib/assets.d.ts.map +1 -0
  29. package/lib/assets.js +35 -0
  30. package/lib/assets.js.map +1 -0
  31. package/lib/commands.d.ts +14 -0
  32. package/lib/commands.d.ts.map +1 -0
  33. package/lib/commands.js +100 -0
  34. package/lib/commands.js.map +1 -0
  35. package/lib/config.d.ts +25 -0
  36. package/lib/config.d.ts.map +1 -0
  37. package/lib/config.js +85 -0
  38. package/lib/config.js.map +1 -0
  39. package/lib/package-root.d.ts +2 -0
  40. package/lib/package-root.d.ts.map +1 -0
  41. package/lib/package-root.js +27 -0
  42. package/lib/package-root.js.map +1 -0
  43. package/lib/plan.d.ts +33 -0
  44. package/lib/plan.d.ts.map +1 -0
  45. package/lib/plan.js +36 -0
  46. package/lib/plan.js.map +1 -0
  47. package/lib/sections.d.ts +38 -0
  48. package/lib/sections.d.ts.map +1 -0
  49. package/lib/sections.js +93 -0
  50. package/lib/sections.js.map +1 -0
  51. package/lib/stamp.d.ts +22 -0
  52. package/lib/stamp.d.ts.map +1 -0
  53. package/lib/stamp.js +57 -0
  54. package/lib/stamp.js.map +1 -0
  55. package/lib/sync.d.ts +13 -0
  56. package/lib/sync.d.ts.map +1 -0
  57. package/lib/sync.js +59 -0
  58. package/lib/sync.js.map +1 -0
  59. package/lib/vars.d.ts +20 -0
  60. package/lib/vars.d.ts.map +1 -0
  61. package/lib/vars.js +38 -0
  62. package/lib/vars.js.map +1 -0
  63. package/package.json +42 -0
  64. package/rt-tools-agent-kit-0.1.0.tgz +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,MAAM,CAAC,MAAM,KAAK,GAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;AAW1F,MAAM,CAAC,MAAM,WAAW,GAAW,qBAAqB,CAAC;AACzD,MAAM,CAAC,MAAM,aAAa,GAAW,0BAA0B,CAAC;AAEhE;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAoC;IAC3D,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,eAAe;IACtB,MAAM,EAAE,OAAO;IACf,MAAM,EAAE,gBAAgB;IACxB,SAAS,EAAE,0BAA0B;CACxC,CAAC;AAEF,MAAM,OAAO,WAAY,SAAQ,KAAK;CAAG;AAEzC,MAAM,QAAQ,GAAyD,CAAC,KAAc,EAAoC,EAAE,CACxH,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEzE,SAAS,SAAS,CAAC,KAAc,EAAE,KAAa;IAC5C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACd,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,qCAAqC,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,IAAI,GAAG,oBAAoB,CAAC,CAAC;QAC/D,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACxB,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACpC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACD,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACtB,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,+BAAgC,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,WAAW,CAAC,GAAG,WAAW,oBAAoB,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,MAAM,GAA2B,EAAE,GAAG,cAAc,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;IACpG,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAa,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,WAAW,CAAC,UAAU,IAAI,qCAAqC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjG,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAY,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACxC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,KAAc,EAAW,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,EAAE,CAAC;QAC5F,MAAM,IAAI,WAAW,CAAC,iDAAiD,CAAC,CAAC;IAC7E,CAAC;IAED,4FAA4F;IAC5F,8EAA8E;IAC9E,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAA+B,EAAE,IAAI,EAAE,IAAgB,EAAE,CAAC;AACxI,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,UAAU,CAAC,IAAY;IACnC,IAAI,CAAC;QACD,OAAO,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACtB,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YAC/B,MAAM,KAAK,CAAC;QAChB,CAAC;QAED,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC","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 /** Ресурсы, от которых проект отказался, идентификаторами: `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\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 skip: unknown = raw['skip'] ?? [];\n if (!Array.isArray(skip) || skip.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError('skip: ожидается список идентификаторов ресурсов');\n }\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>, skip: skip as string[] };\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,2 @@
1
+ export declare function packageRootFrom(start: string): string;
2
+ //# sourceMappingURL=package-root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-root.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/package-root.ts"],"names":[],"mappings":"AAcA,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYrD"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Корень пакета — каталог, где лежат `package.json` и `assets/`.
3
+ *
4
+ * Считать его отступом от файла нельзя: в исходниках модуль лежит на `src/lib/`, а в собранном
5
+ * пакете — на `lib/`, и один и тот же `../..` в первом случае приводит в корень пакета, а во
6
+ * втором — на каталог выше. Прогон собранного CLI падал на чтении `package.json` ровно так.
7
+ *
8
+ * Поэтому корень ищется подъёмом до первого `package.json`. Откуда подниматься, говорит
9
+ * вызывающий: `import.meta.url` живёт только в точке входа, которую не тянет в себя ни одна
10
+ * спека, — трансформ тестов разбирает модули как CommonJS, и это выражение для него синтаксис.
11
+ */
12
+ import { existsSync } from 'node:fs';
13
+ import { dirname, join } from 'node:path';
14
+ export function packageRootFrom(start) {
15
+ let dir = start;
16
+ for (;;) {
17
+ if (existsSync(join(dir, 'package.json'))) {
18
+ return dir;
19
+ }
20
+ const parent = dirname(dir);
21
+ if (parent === dir) {
22
+ throw new Error(`не найден корень пакета: подъём от ${start} не встретил package.json`);
23
+ }
24
+ dir = parent;
25
+ }
26
+ }
27
+ //# sourceMappingURL=package-root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-root.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/package-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,UAAU,eAAe,CAAC,KAAa;IACzC,IAAI,GAAG,GAAW,KAAK,CAAC;IACxB,SAAS,CAAC;QACN,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;YACxC,OAAO,GAAG,CAAC;QACf,CAAC;QACD,MAAM,MAAM,GAAW,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,sCAAsC,KAAK,2BAA2B,CAAC,CAAC;QAC5F,CAAC;QACD,GAAG,GAAG,MAAM,CAAC;IACjB,CAAC;AACL,CAAC","sourcesContent":["/**\n * Корень пакета — каталог, где лежат `package.json` и `assets/`.\n *\n * Считать его отступом от файла нельзя: в исходниках модуль лежит на `src/lib/`, а в собранном\n * пакете — на `lib/`, и один и тот же `../..` в первом случае приводит в корень пакета, а во\n * втором — на каталог выше. Прогон собранного CLI падал на чтении `package.json` ровно так.\n *\n * Поэтому корень ищется подъёмом до первого `package.json`. Откуда подниматься, говорит\n * вызывающий: `import.meta.url` живёт только в точке входа, которую не тянет в себя ни одна\n * спека, — трансформ тестов разбирает модули как CommonJS, и это выражение для него синтаксис.\n */\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nexport function packageRootFrom(start: string): string {\n let dir: string = start;\n for (;;) {\n if (existsSync(join(dir, 'package.json'))) {\n return dir;\n }\n const parent: string = dirname(dir);\n if (parent === dir) {\n throw new Error(`не найден корень пакета: подъём от ${start} не встретил package.json`);\n }\n dir = parent;\n }\n}\n"]}
package/lib/plan.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ export type TOutcome =
2
+ /** Файла нет — положить. */
3
+ 'create'
4
+ /** Разложенное отстало от пакета или от надстройки — переложить. */
5
+ | 'update'
6
+ /** Тело не сходится с шапкой: файл правили руками. */
7
+ | 'drift'
8
+ /** Файл есть, шапки нет — положен не пакетом, трогать нельзя. */
9
+ | 'foreign'
10
+ /** Сходится всё. */
11
+ | 'ok';
12
+ export interface IPlanInput {
13
+ readonly path: string;
14
+ readonly asset: string;
15
+ readonly version: string;
16
+ /** Тело после подстановки значений и слияния с надстройкой. */
17
+ readonly rendered: string;
18
+ /** Что лежит на этом пути сейчас; `null` — файла нет. */
19
+ readonly existing: string | null;
20
+ }
21
+ export interface IPlanned {
22
+ readonly path: string;
23
+ readonly asset: string;
24
+ readonly outcome: TOutcome;
25
+ /** Что записать. У `drift`, `foreign` и `ok` записывать нечего. */
26
+ readonly content: string | null;
27
+ }
28
+ export declare function planFile(input: IPlanInput): IPlanned;
29
+ /** Раскладка не удалась, если хоть один файл её не принял. */
30
+ export declare const isRefusal: (outcome: TOutcome) => boolean;
31
+ /** Что `sync --check` считает расхождением: и отказ, и всё, что осталось не переложенным. */
32
+ export declare const isPending: (outcome: TOutcome) => boolean;
33
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/plan.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,QAAQ;AAChB,4BAA4B;AAC1B,QAAQ;AACV,oEAAoE;GAClE,QAAQ;AACV,sDAAsD;GACpD,OAAO;AACT,iEAAiE;GAC/D,SAAS;AACX,oBAAoB;GAClB,IAAI,CAAC;AAEX,MAAM,WAAW,UAAU;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,CA0BpD;AAED,8DAA8D;AAC9D,eAAO,MAAM,SAAS,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAsF,CAAC;AAEtI,6FAA6F;AAC7F,eAAO,MAAM,SAAS,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAA0D,CAAC"}
package/lib/plan.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Что случится с одним файлом на раскладке — решение без обращения к диску.
3
+ *
4
+ * Отделено от чтения и записи потому, что решений здесь пять, а различить их надо до того, как
5
+ * что-нибудь записано: `sync --check` в гейте пуша ничего не пишет вовсе, а обычный `sync`
6
+ * обязан отказаться от файла, который правили руками, а не молча его переписать.
7
+ */
8
+ import { applyStamp, digestOf, readStamped } from './stamp.js';
9
+ export function planFile(input) {
10
+ const stamp = { version: input.version, asset: input.asset, hash: digestOf(input.rendered) };
11
+ const content = applyStamp(input.rendered, stamp, input.path);
12
+ const planned = (outcome, write) => ({
13
+ path: input.path,
14
+ asset: input.asset,
15
+ outcome,
16
+ content: write ? content : null,
17
+ });
18
+ if (input.existing === null) {
19
+ return planned('create', true);
20
+ }
21
+ const found = readStamped(input.existing);
22
+ if (!found) {
23
+ return planned('foreign', false);
24
+ }
25
+ // Порядок проверок решает: у файла, который и правили руками, и отставшего, надо назвать
26
+ // правку. Переложить его — потерять её, а разбирать нечего: в дереве её уже нет.
27
+ if (digestOf(found.body) !== found.stamp.hash) {
28
+ return planned('drift', false);
29
+ }
30
+ return found.stamp.hash === stamp.hash && found.stamp.version === input.version ? planned('ok', false) : planned('update', true);
31
+ }
32
+ /** Раскладка не удалась, если хоть один файл её не принял. */
33
+ export const isRefusal = (outcome) => outcome === 'drift' || outcome === 'foreign';
34
+ /** Что `sync --check` считает расхождением: и отказ, и всё, что осталось не переложенным. */
35
+ export const isPending = (outcome) => outcome !== 'ok';
36
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/plan.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAoB,WAAW,EAAE,MAAM,YAAY,CAAC;AAgCjF,MAAM,UAAU,QAAQ,CAAC,KAAiB;IACtC,MAAM,KAAK,GAAW,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;IACrG,MAAM,OAAO,GAAW,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,OAAO,GAAoD,CAAC,OAAiB,EAAE,KAAc,EAAY,EAAE,CAAC,CAAC;QAC/G,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO;QACP,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;KAClC,CAAC,CAAC;IAEH,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,KAAK,GAAoB,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,yFAAyF;IACzF,iFAAiF;IACjF,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC5C,OAAO,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACrI,CAAC;AAED,8DAA8D;AAC9D,MAAM,CAAC,MAAM,SAAS,GAAmC,CAAC,OAAiB,EAAW,EAAE,CAAC,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,SAAS,CAAC;AAEtI,6FAA6F;AAC7F,MAAM,CAAC,MAAM,SAAS,GAAmC,CAAC,OAAiB,EAAW,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC","sourcesContent":["/**\n * Что случится с одним файлом на раскладке — решение без обращения к диску.\n *\n * Отделено от чтения и записи потому, что решений здесь пять, а различить их надо до того, как\n * что-нибудь записано: `sync --check` в гейте пуша ничего не пишет вовсе, а обычный `sync`\n * обязан отказаться от файла, который правили руками, а не молча его переписать.\n */\nimport { applyStamp, digestOf, IStamp, IStamped, readStamped } from './stamp.js';\n\nexport type TOutcome =\n /** Файла нет — положить. */\n | 'create'\n /** Разложенное отстало от пакета или от надстройки — переложить. */\n | 'update'\n /** Тело не сходится с шапкой: файл правили руками. */\n | 'drift'\n /** Файл есть, шапки нет — положен не пакетом, трогать нельзя. */\n | 'foreign'\n /** Сходится всё. */\n | 'ok';\n\nexport interface IPlanInput {\n readonly path: string;\n readonly asset: string;\n readonly version: string;\n /** Тело после подстановки значений и слияния с надстройкой. */\n readonly rendered: string;\n /** Что лежит на этом пути сейчас; `null` — файла нет. */\n readonly existing: string | null;\n}\n\nexport interface IPlanned {\n readonly path: string;\n readonly asset: string;\n readonly outcome: TOutcome;\n /** Что записать. У `drift`, `foreign` и `ok` записывать нечего. */\n readonly content: string | null;\n}\n\nexport function planFile(input: IPlanInput): IPlanned {\n const stamp: IStamp = { version: input.version, asset: input.asset, hash: digestOf(input.rendered) };\n const content: string = applyStamp(input.rendered, stamp, input.path);\n const planned: (outcome: TOutcome, write: boolean) => IPlanned = (outcome: TOutcome, write: boolean): IPlanned => ({\n path: input.path,\n asset: input.asset,\n outcome,\n content: write ? content : null,\n });\n\n if (input.existing === null) {\n return planned('create', true);\n }\n\n const found: IStamped | null = readStamped(input.existing);\n if (!found) {\n return planned('foreign', false);\n }\n\n // Порядок проверок решает: у файла, который и правили руками, и отставшего, надо назвать\n // правку. Переложить его — потерять её, а разбирать нечего: в дереве её уже нет.\n if (digestOf(found.body) !== found.stamp.hash) {\n return planned('drift', false);\n }\n\n return found.stamp.hash === stamp.hash && found.stamp.version === input.version ? planned('ok', false) : planned('update', true);\n}\n\n/** Раскладка не удалась, если хоть один файл её не принял. */\nexport const isRefusal: (outcome: TOutcome) => boolean = (outcome: TOutcome): boolean => outcome === 'drift' || outcome === 'foreign';\n\n/** Что `sync --check` считает расхождением: и отказ, и всё, что осталось не переложенным. */\nexport const isPending: (outcome: TOutcome) => boolean = (outcome: TOutcome): boolean => outcome !== 'ok';\n"]}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Разбор документа на разделы `## ` и слияние надстройки проекта с текстом пакета.
3
+ *
4
+ * Пакет везёт общий текст, проект дописывает своё. Заменять файл целиком нельзя — тогда правка
5
+ * пакета до проекта не доедет и этого никто не заметит; не заменять ничего тоже нельзя — общий
6
+ * текст всегда чем-то расходится с местом, куда его положили.
7
+ *
8
+ * Отсюда единица слияния — раздел `## `: он крупнее строки, поэтому слияние не рвёт фразу
9
+ * посередине, и мельче файла, поэтому правка пакета доезжает во все разделы, которых проект не
10
+ * трогал.
11
+ */
12
+ /** Раздел документа: заголовок `## …` и всё до следующего заголовка того же уровня. */
13
+ export interface ISection {
14
+ /** Строка заголовка целиком, вместе с решётками. */
15
+ readonly heading: string;
16
+ /** Текст под заголовком без краевых пустых строк. */
17
+ readonly body: string;
18
+ }
19
+ /** Документ: вступление до первого `## ` и разделы по порядку. */
20
+ export interface IDocument {
21
+ readonly preamble: string;
22
+ readonly sections: readonly ISection[];
23
+ }
24
+ export declare function parseDocument(text: string): IDocument;
25
+ export declare function renderDocument(document: IDocument): string;
26
+ /**
27
+ * Текст пакета с наложенной надстройкой проекта.
28
+ *
29
+ * Три случая, и нужны все три: совпавший заголовок **замещает** раздел пакета, новый —
30
+ * **дописывается** в конец, пустой — **снимает** раздел. Без снятия проект не мог бы отказаться
31
+ * от общего раздела иначе, чем правкой разложенного файла, а такая правка теряется на следующем
32
+ * же `sync`.
33
+ *
34
+ * Вступление надстройки замещает вступление пакета, только если оно не пустое: у надстройки,
35
+ * правящей один раздел, вступления нет вовсе.
36
+ */
37
+ export declare function mergeDocuments(base: IDocument, override: IDocument | null): IDocument;
38
+ //# sourceMappingURL=sections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sections.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,uFAAuF;AACvF,MAAM,WAAW,QAAQ;IACrB,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,EAAE,CAAC;CAC1C;AASD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAgCrD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,GAAG,MAAM,CAO1D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAgCrF"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Разбор документа на разделы `## ` и слияние надстройки проекта с текстом пакета.
3
+ *
4
+ * Пакет везёт общий текст, проект дописывает своё. Заменять файл целиком нельзя — тогда правка
5
+ * пакета до проекта не доедет и этого никто не заметит; не заменять ничего тоже нельзя — общий
6
+ * текст всегда чем-то расходится с местом, куда его положили.
7
+ *
8
+ * Отсюда единица слияния — раздел `## `: он крупнее строки, поэтому слияние не рвёт фразу
9
+ * посередине, и мельче файла, поэтому правка пакета доезжает во все разделы, которых проект не
10
+ * трогал.
11
+ */
12
+ const SECTION_HEADING = /^##\s+\S/;
13
+ /** Ограда блока кода: заголовок внутри неё — часть примера, а не раздел документа. */
14
+ const FENCE = /^\s*(```|~~~)/;
15
+ const trimEdges = (lines) => lines.join('\n').replace(/^\n+/, '').replace(/\s+$/, '');
16
+ export function parseDocument(text) {
17
+ const preamble = [];
18
+ const sections = [];
19
+ let heading = null;
20
+ let body = [];
21
+ let fenced = false;
22
+ const flush = () => {
23
+ if (heading !== null) {
24
+ sections.push({ heading, body: trimEdges(body) });
25
+ }
26
+ };
27
+ for (const line of text.split('\n')) {
28
+ if (FENCE.test(line)) {
29
+ fenced = !fenced;
30
+ }
31
+ if (!fenced && SECTION_HEADING.test(line)) {
32
+ flush();
33
+ heading = line.trimEnd();
34
+ body = [];
35
+ continue;
36
+ }
37
+ if (heading === null) {
38
+ preamble.push(line);
39
+ }
40
+ else {
41
+ body.push(line);
42
+ }
43
+ }
44
+ flush();
45
+ return { preamble: trimEdges(preamble), sections };
46
+ }
47
+ export function renderDocument(document) {
48
+ const parts = document.preamble ? [document.preamble] : [];
49
+ for (const section of document.sections) {
50
+ parts.push(section.body ? `${section.heading}\n\n${section.body}` : section.heading);
51
+ }
52
+ return `${parts.join('\n\n')}\n`;
53
+ }
54
+ /**
55
+ * Текст пакета с наложенной надстройкой проекта.
56
+ *
57
+ * Три случая, и нужны все три: совпавший заголовок **замещает** раздел пакета, новый —
58
+ * **дописывается** в конец, пустой — **снимает** раздел. Без снятия проект не мог бы отказаться
59
+ * от общего раздела иначе, чем правкой разложенного файла, а такая правка теряется на следующем
60
+ * же `sync`.
61
+ *
62
+ * Вступление надстройки замещает вступление пакета, только если оно не пустое: у надстройки,
63
+ * правящей один раздел, вступления нет вовсе.
64
+ */
65
+ export function mergeDocuments(base, override) {
66
+ if (!override) {
67
+ return base;
68
+ }
69
+ const byHeading = new Map();
70
+ for (const section of override.sections) {
71
+ byHeading.set(section.heading, section);
72
+ }
73
+ const taken = new Set();
74
+ const sections = [];
75
+ for (const section of base.sections) {
76
+ const replacement = byHeading.get(section.heading);
77
+ if (!replacement) {
78
+ sections.push(section);
79
+ continue;
80
+ }
81
+ taken.add(section.heading);
82
+ if (replacement.body) {
83
+ sections.push(replacement);
84
+ }
85
+ }
86
+ for (const section of override.sections) {
87
+ if (!taken.has(section.heading) && section.body) {
88
+ sections.push(section);
89
+ }
90
+ }
91
+ return { preamble: override.preamble || base.preamble, sections };
92
+ }
93
+ //# sourceMappingURL=sections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sections.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Разбор документа на разделы `## ` и слияние надстройки проекта с текстом пакета.\n *\n * Пакет везёт общий текст, проект дописывает своё. Заменять файл целиком нельзя — тогда правка\n * пакета до проекта не доедет и этого никто не заметит; не заменять ничего тоже нельзя — общий\n * текст всегда чем-то расходится с местом, куда его положили.\n *\n * Отсюда единица слияния — раздел `## `: он крупнее строки, поэтому слияние не рвёт фразу\n * посередине, и мельче файла, поэтому правка пакета доезжает во все разделы, которых проект не\n * трогал.\n */\n\n/** Раздел документа: заголовок `## …` и всё до следующего заголовка того же уровня. */\nexport interface ISection {\n /** Строка заголовка целиком, вместе с решётками. */\n readonly heading: string;\n /** Текст под заголовком без краевых пустых строк. */\n readonly body: string;\n}\n\n/** Документ: вступление до первого `## ` и разделы по порядку. */\nexport interface IDocument {\n readonly preamble: string;\n readonly sections: readonly ISection[];\n}\n\nconst SECTION_HEADING: RegExp = /^##\\s+\\S/;\n/** Ограда блока кода: заголовок внутри неё — часть примера, а не раздел документа. */\nconst FENCE: RegExp = /^\\s*(```|~~~)/;\n\nconst trimEdges: (lines: readonly string[]) => string = (lines: readonly string[]): string =>\n lines.join('\\n').replace(/^\\n+/, '').replace(/\\s+$/, '');\n\nexport function parseDocument(text: string): IDocument {\n const preamble: string[] = [];\n const sections: ISection[] = [];\n let heading: string | null = null;\n let body: string[] = [];\n let fenced: boolean = false;\n\n const flush: () => void = (): void => {\n if (heading !== null) {\n sections.push({ heading, body: trimEdges(body) });\n }\n };\n\n for (const line of text.split('\\n')) {\n if (FENCE.test(line)) {\n fenced = !fenced;\n }\n if (!fenced && SECTION_HEADING.test(line)) {\n flush();\n heading = line.trimEnd();\n body = [];\n continue;\n }\n if (heading === null) {\n preamble.push(line);\n } else {\n body.push(line);\n }\n }\n flush();\n\n return { preamble: trimEdges(preamble), sections };\n}\n\nexport function renderDocument(document: IDocument): string {\n const parts: string[] = document.preamble ? [document.preamble] : [];\n for (const section of document.sections) {\n parts.push(section.body ? `${section.heading}\\n\\n${section.body}` : section.heading);\n }\n\n return `${parts.join('\\n\\n')}\\n`;\n}\n\n/**\n * Текст пакета с наложенной надстройкой проекта.\n *\n * Три случая, и нужны все три: совпавший заголовок **замещает** раздел пакета, новый —\n * **дописывается** в конец, пустой — **снимает** раздел. Без снятия проект не мог бы отказаться\n * от общего раздела иначе, чем правкой разложенного файла, а такая правка теряется на следующем\n * же `sync`.\n *\n * Вступление надстройки замещает вступление пакета, только если оно не пустое: у надстройки,\n * правящей один раздел, вступления нет вовсе.\n */\nexport function mergeDocuments(base: IDocument, override: IDocument | null): IDocument {\n if (!override) {\n return base;\n }\n\n const byHeading: Map<string, ISection> = new Map();\n for (const section of override.sections) {\n byHeading.set(section.heading, section);\n }\n\n const taken: Set<string> = new Set();\n const sections: ISection[] = [];\n\n for (const section of base.sections) {\n const replacement: ISection | undefined = byHeading.get(section.heading);\n if (!replacement) {\n sections.push(section);\n continue;\n }\n taken.add(section.heading);\n if (replacement.body) {\n sections.push(replacement);\n }\n }\n\n for (const section of override.sections) {\n if (!taken.has(section.heading) && section.body) {\n sections.push(section);\n }\n }\n\n return { preamble: override.preamble || base.preamble, sections };\n}\n"]}
package/lib/stamp.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ export interface IStamp {
2
+ readonly version: string;
3
+ /** Идентификатор ресурса в пакете, он же путь надстройки. */
4
+ readonly asset: string;
5
+ /** Сумма тела, каким его положил пакет. */
6
+ readonly hash: string;
7
+ }
8
+ export interface IStamped {
9
+ readonly stamp: IStamp;
10
+ readonly body: string;
11
+ }
12
+ /** Двенадцати шестнадцатеричных знаков хватает: шапку читает человек, а не сверяет сеть. */
13
+ export declare const digestOf: (text: string) => string;
14
+ export declare function formatStamp(stamp: IStamp, path: string): string;
15
+ /**
16
+ * Шапка и тело разложенного файла. Ищется она в первых двух строках: у скрипта первой стоит
17
+ * `#!`, и требовать шапку строго первой значило бы ломать запуск.
18
+ */
19
+ export declare function readStamped(text: string): IStamped | null;
20
+ /** Тело с шапкой: у скрипта она встаёт после строки запуска, у остальных — первой. */
21
+ export declare function applyStamp(body: string, stamp: IStamp, path: string): string;
22
+ //# sourceMappingURL=stamp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamp.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/stamp.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,MAAM;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED,4FAA4F;AAC5F,eAAO,MAAM,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAC+B,CAAC;AAyBzE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAYzD;AAED,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAS5E"}
package/lib/stamp.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Шапка разложенного файла: чем он положен, какой версией и то ли у него тело, которое пакет
3
+ * клал.
4
+ *
5
+ * Разложенное коммитится — иначе его не видно ни в разборе ветки, ни на машине без установки
6
+ * пакета. А раз коммитится, его правят руками: файл лежит в дереве проекта и выглядит обычным.
7
+ * Правка при этом теряется на следующем `sync` молча, и «правило перестало действовать»
8
+ * замечают уже по последствиям.
9
+ *
10
+ * Отсюда сумма тела в шапке. Она отвечает сразу на два вопроса, и оба нужны:
11
+ *
12
+ * - сумма не сходится с **фактическим** телом — файл правили руками;
13
+ * - сумма не сходится со **свежесобранным** телом — разложенное отстало от пакета или от
14
+ * надстройки, и его надо переложить.
15
+ */
16
+ import { createHash } from 'node:crypto';
17
+ /** Двенадцати шестнадцатеричных знаков хватает: шапку читает человек, а не сверяет сеть. */
18
+ export const digestOf = (text) => createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 12);
19
+ const COMMENTS = new Map([
20
+ ['md', { open: '<!-- ', close: ' -->' }],
21
+ ['sh', { open: '# ', close: '' }],
22
+ ['mjs', { open: '// ', close: '' }],
23
+ ['js', { open: '// ', close: '' }],
24
+ ['ts', { open: '// ', close: '' }],
25
+ ]);
26
+ const commentOf = (path) => COMMENTS.get(path.split('.').pop() ?? '') ?? { open: '# ', close: '' };
27
+ const STAMP_LINE = /rt-kit\s+v(\S+)\s+·\s+(\S+)\s+·\s+([0-9a-f]{12})/;
28
+ export function formatStamp(stamp, path) {
29
+ const comment = commentOf(path);
30
+ const text = `rt-kit v${stamp.version} · ${stamp.asset} · ${stamp.hash} · правится надстройкой, не здесь`;
31
+ return `${comment.open}${text}${comment.close}`;
32
+ }
33
+ /**
34
+ * Шапка и тело разложенного файла. Ищется она в первых двух строках: у скрипта первой стоит
35
+ * `#!`, и требовать шапку строго первой значило бы ломать запуск.
36
+ */
37
+ export function readStamped(text) {
38
+ const lines = text.split('\n');
39
+ const index = lines.slice(0, 2).findIndex((line) => STAMP_LINE.test(line));
40
+ if (index < 0) {
41
+ return null;
42
+ }
43
+ const found = lines[index].match(STAMP_LINE);
44
+ const [, version, asset, hash] = found;
45
+ const body = [...lines.slice(0, index), ...lines.slice(index + 1)].join('\n');
46
+ return { stamp: { version, asset, hash }, body };
47
+ }
48
+ /** Тело с шапкой: у скрипта она встаёт после строки запуска, у остальных — первой. */
49
+ export function applyStamp(body, stamp, path) {
50
+ const line = formatStamp(stamp, path);
51
+ if (body.startsWith('#!')) {
52
+ const cut = body.indexOf('\n');
53
+ return `${body.slice(0, cut + 1)}${line}\n${body.slice(cut + 1)}`;
54
+ }
55
+ return `${line}\n${body}`;
56
+ }
57
+ //# sourceMappingURL=stamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamp.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/stamp.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Шапка разложенного файла: чем он положен, какой версией и то ли у него тело, которое пакет\n * клал.\n *\n * Разложенное коммитится — иначе его не видно ни в разборе ветки, ни на машине без установки\n * пакета. А раз коммитится, его правят руками: файл лежит в дереве проекта и выглядит обычным.\n * Правка при этом теряется на следующем `sync` молча, и «правило перестало действовать»\n * замечают уже по последствиям.\n *\n * Отсюда сумма тела в шапке. Она отвечает сразу на два вопроса, и оба нужны:\n *\n * - сумма не сходится с **фактическим** телом — файл правили руками;\n * - сумма не сходится со **свежесобранным** телом — разложенное отстало от пакета или от\n * надстройки, и его надо переложить.\n */\nimport { createHash } from 'node:crypto';\n\nexport interface IStamp {\n readonly version: string;\n /** Идентификатор ресурса в пакете, он же путь надстройки. */\n readonly asset: string;\n /** Сумма тела, каким его положил пакет. */\n readonly hash: string;\n}\n\nexport interface IStamped {\n readonly stamp: IStamp;\n readonly body: string;\n}\n\n/** Двенадцати шестнадцатеричных знаков хватает: шапку читает человек, а не сверяет сеть. */\nexport const digestOf: (text: string) => string = (text: string): string =>\n createHash('sha256').update(text, 'utf8').digest('hex').slice(0, 12);\n\n/**\n * Комментарий, невидимый при показе разметки, и обычный комментарий для всего остального.\n * Незнакомое расширение получает решётку: она комментарий в оболочке, в конфигах и в тексте, и\n * ошибиться ею дешевле, чем промолчать.\n */\ninterface IComment {\n readonly open: string;\n readonly close: string;\n}\n\nconst COMMENTS: ReadonlyMap<string, IComment> = new Map<string, IComment>([\n ['md', { open: '<!-- ', close: ' -->' }],\n ['sh', { open: '# ', close: '' }],\n ['mjs', { open: '// ', close: '' }],\n ['js', { open: '// ', close: '' }],\n ['ts', { open: '// ', close: '' }],\n]);\n\nconst commentOf: (path: string) => IComment = (path: string): IComment =>\n COMMENTS.get(path.split('.').pop() ?? '') ?? { open: '# ', close: '' };\n\nconst STAMP_LINE: RegExp = /rt-kit\\s+v(\\S+)\\s+·\\s+(\\S+)\\s+·\\s+([0-9a-f]{12})/;\n\nexport function formatStamp(stamp: IStamp, path: string): string {\n const comment: IComment = commentOf(path);\n const text: string = `rt-kit v${stamp.version} · ${stamp.asset} · ${stamp.hash} · правится надстройкой, не здесь`;\n\n return `${comment.open}${text}${comment.close}`;\n}\n\n/**\n * Шапка и тело разложенного файла. Ищется она в первых двух строках: у скрипта первой стоит\n * `#!`, и требовать шапку строго первой значило бы ломать запуск.\n */\nexport function readStamped(text: string): IStamped | null {\n const lines: string[] = text.split('\\n');\n const index: number = lines.slice(0, 2).findIndex((line: string): boolean => STAMP_LINE.test(line));\n if (index < 0) {\n return null;\n }\n\n const found: RegExpMatchArray = lines[index].match(STAMP_LINE) as RegExpMatchArray;\n const [, version, asset, hash]: string[] = found;\n const body: string = [...lines.slice(0, index), ...lines.slice(index + 1)].join('\\n');\n\n return { stamp: { version, asset, hash }, body };\n}\n\n/** Тело с шапкой: у скрипта она встаёт после строки запуска, у остальных — первой. */\nexport function applyStamp(body: string, stamp: IStamp, path: string): string {\n const line: string = formatStamp(stamp, path);\n if (body.startsWith('#!')) {\n const cut: number = body.indexOf('\\n');\n\n return `${body.slice(0, cut + 1)}${line}\\n${body.slice(cut + 1)}`;\n }\n\n return `${line}\\n${body}`;\n}\n"]}
package/lib/sync.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { IConfig } from './config.js';
2
+ import { IPlanned } from './plan.js';
3
+ export interface ISyncResult {
4
+ readonly planned: readonly IPlanned[];
5
+ /** Дырки без значения, по ресурсам. Непустой список — отказ: подставлять нечего. */
6
+ readonly missing: ReadonlyMap<string, readonly string[]>;
7
+ readonly written: readonly string[];
8
+ }
9
+ export declare function planSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult;
10
+ /** Раскладка. Отказ хотя бы по одному файлу не пишет ничего: половина разложенного хуже целого. */
11
+ export declare function runSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult;
12
+ export declare const pendingOf: (result: ISyncResult) => readonly IPlanned[];
13
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAkC,MAAM,WAAW,CAAC;AAIrE,MAAM,WAAW,WAAW;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,oFAAoF;IACpF,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IACzD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAiBD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAgBvG;AAED,mGAAmG;AACnG,wBAAgB,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAkBtG;AAED,eAAO,MAAM,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,SAAS,QAAQ,EACe,CAAC"}
package/lib/sync.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Раскладка ресурсов пакета в дерево проекта.
3
+ *
4
+ * Порядок один и тот же и для записи, и для проверки: собрать тело, решить судьбу файла, и
5
+ * только потом писать. `sync --check` отличается от `sync` ровно последним шагом — иначе гейт
6
+ * пуша проверял бы не то, что кладёт раскладка.
7
+ */
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
+ import { dirname, join } from 'node:path';
10
+ import { collectAssets } from './assets.js';
11
+ import { OVERRIDES_DIR } from './config.js';
12
+ import { isPending, isRefusal, planFile } from './plan.js';
13
+ import { mergeDocuments, parseDocument, renderDocument } from './sections.js';
14
+ import { renderVars } from './vars.js';
15
+ const read = (path) => (existsSync(path) ? readFileSync(path, 'utf8') : null);
16
+ /**
17
+ * Тело ресурса: текст пакета, поверх него надстройка проекта, и уже потом подстановка значений.
18
+ *
19
+ * Порядок именно такой. Надстройка тоже пишется с дырками — иначе проект, дописавший раздел про
20
+ * свою главную ветку, зашил бы её имя в двух местах: в конфиге и в тексте.
21
+ */
22
+ function renderAsset(asset, config, root) {
23
+ const override = read(join(root, OVERRIDES_DIR, asset.id));
24
+ const merged = override ? renderDocument(mergeDocuments(parseDocument(asset.text), parseDocument(override))) : asset.text;
25
+ return renderVars(merged, config.vars);
26
+ }
27
+ export function planSync(config, root, version, assetsDir) {
28
+ const planned = [];
29
+ const missing = new Map();
30
+ for (const asset of collectAssets(config, assetsDir)) {
31
+ const rendered = renderAsset(asset, config, root);
32
+ if (rendered.missing.length) {
33
+ missing.set(asset.id, rendered.missing);
34
+ continue;
35
+ }
36
+ planned.push(planFile({ path: asset.target, asset: asset.id, version, rendered: rendered.text, existing: read(join(root, asset.target)) }));
37
+ }
38
+ return { planned, missing, written: [] };
39
+ }
40
+ /** Раскладка. Отказ хотя бы по одному файлу не пишет ничего: половина разложенного хуже целого. */
41
+ export function runSync(config, root, version, assetsDir) {
42
+ const result = planSync(config, root, version, assetsDir);
43
+ if (result.missing.size || result.planned.some((entry) => isRefusal(entry.outcome))) {
44
+ return result;
45
+ }
46
+ const written = [];
47
+ for (const entry of result.planned) {
48
+ if (entry.content === null) {
49
+ continue;
50
+ }
51
+ const path = join(root, entry.path);
52
+ mkdirSync(dirname(path), { recursive: true });
53
+ writeFileSync(path, entry.content, 'utf8');
54
+ written.push(entry.path);
55
+ }
56
+ return { ...result, written };
57
+ }
58
+ export const pendingOf = (result) => result.planned.filter((entry) => isPending(entry.outcome));
59
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Раскладка ресурсов пакета в дерево проекта.\n *\n * Порядок один и тот же и для записи, и для проверки: собрать тело, решить судьбу файла, и\n * только потом писать. `sync --check` отличается от `sync` ровно последним шагом — иначе гейт\n * пуша проверял бы не то, что кладёт раскладка.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport { collectAssets, IAsset } from './assets.js';\nimport { IConfig, OVERRIDES_DIR } from './config.js';\nimport { IPlanned, isPending, isRefusal, planFile } from './plan.js';\nimport { mergeDocuments, parseDocument, renderDocument } from './sections.js';\nimport { IRenderResult, renderVars } from './vars.js';\n\nexport interface ISyncResult {\n readonly planned: readonly IPlanned[];\n /** Дырки без значения, по ресурсам. Непустой список — отказ: подставлять нечего. */\n readonly missing: ReadonlyMap<string, readonly string[]>;\n readonly written: readonly string[];\n}\n\nconst read: (path: string) => string | null = (path: string): string | null => (existsSync(path) ? readFileSync(path, 'utf8') : null);\n\n/**\n * Тело ресурса: текст пакета, поверх него надстройка проекта, и уже потом подстановка значений.\n *\n * Порядок именно такой. Надстройка тоже пишется с дырками — иначе проект, дописавший раздел про\n * свою главную ветку, зашил бы её имя в двух местах: в конфиге и в тексте.\n */\nfunction renderAsset(asset: IAsset, config: IConfig, root: string): IRenderResult {\n const override: string | null = read(join(root, OVERRIDES_DIR, asset.id));\n const merged: string = override ? renderDocument(mergeDocuments(parseDocument(asset.text), parseDocument(override))) : asset.text;\n\n return renderVars(merged, config.vars);\n}\n\nexport function planSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult {\n const planned: IPlanned[] = [];\n const missing: Map<string, readonly string[]> = new Map();\n\n for (const asset of collectAssets(config, assetsDir)) {\n const rendered: IRenderResult = renderAsset(asset, config, root);\n if (rendered.missing.length) {\n missing.set(asset.id, rendered.missing);\n continue;\n }\n planned.push(\n planFile({ path: asset.target, asset: asset.id, version, rendered: rendered.text, existing: read(join(root, asset.target)) })\n );\n }\n\n return { planned, missing, written: [] };\n}\n\n/** Раскладка. Отказ хотя бы по одному файлу не пишет ничего: половина разложенного хуже целого. */\nexport function runSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult {\n const result: ISyncResult = planSync(config, root, version, assetsDir);\n if (result.missing.size || result.planned.some((entry: IPlanned): boolean => isRefusal(entry.outcome))) {\n return result;\n }\n\n const written: string[] = [];\n for (const entry of result.planned) {\n if (entry.content === null) {\n continue;\n }\n const path: string = join(root, entry.path);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, entry.content, 'utf8');\n written.push(entry.path);\n }\n\n return { ...result, written };\n}\n\nexport const pendingOf: (result: ISyncResult) => readonly IPlanned[] = (result: ISyncResult): readonly IPlanned[] =>\n result.planned.filter((entry: IPlanned): boolean => isPending(entry.outcome));\n"]}
package/lib/vars.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Подстановка значений проекта в текст пакета.
3
+ *
4
+ * Пакет не знает ни префикса компонентов, ни имени главной ветки, ни портов стендов, но говорить
5
+ * о них обязан: правило без этих имён перестаёт быть указанием и превращается в пожелание.
6
+ * Поэтому в тексте пакета стоят дырки `{{имя}}`, а значения приходят из конфига проекта.
7
+ *
8
+ * Неподставленная дырка — **отказ**, а не строка в тексте. Разложенный файл читает агент, и
9
+ * `{{componentPrefix}}` посреди правила он прочтёт как имя: правило станет неверным молча, и
10
+ * заметит это не проверка, а тот, кто будет разбирать последствия.
11
+ */
12
+ export interface IRenderResult {
13
+ readonly text: string;
14
+ /** Имена дырок, которым не нашлось значения, в порядке первого появления. */
15
+ readonly missing: readonly string[];
16
+ }
17
+ export declare function renderVars(text: string, vars: Readonly<Record<string, string>>): IRenderResult;
18
+ /** Имена всех дырок текста — по ним `doctor` говорит, чего проекту не хватает. */
19
+ export declare function placeholdersOf(text: string): readonly string[];
20
+ //# sourceMappingURL=vars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vars.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/vars.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,aAAa,CAiB9F;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAS9D"}
package/lib/vars.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Подстановка значений проекта в текст пакета.
3
+ *
4
+ * Пакет не знает ни префикса компонентов, ни имени главной ветки, ни портов стендов, но говорить
5
+ * о них обязан: правило без этих имён перестаёт быть указанием и превращается в пожелание.
6
+ * Поэтому в тексте пакета стоят дырки `{{имя}}`, а значения приходят из конфига проекта.
7
+ *
8
+ * Неподставленная дырка — **отказ**, а не строка в тексте. Разложенный файл читает агент, и
9
+ * `{{componentPrefix}}` посреди правила он прочтёт как имя: правило станет неверным молча, и
10
+ * заметит это не проверка, а тот, кто будет разбирать последствия.
11
+ */
12
+ /** Дырка в тексте пакета: `{{имя}}`, имя — латиница, цифры, дефис и точка. */
13
+ const PLACEHOLDER = /\{\{([a-zA-Z][\w.-]*)\}\}/g;
14
+ export function renderVars(text, vars) {
15
+ const missing = [];
16
+ const rendered = text.replace(PLACEHOLDER, (whole, name) => {
17
+ const value = vars[name];
18
+ if (value === undefined) {
19
+ if (!missing.includes(name)) {
20
+ missing.push(name);
21
+ }
22
+ return whole;
23
+ }
24
+ return value;
25
+ });
26
+ return { text: rendered, missing };
27
+ }
28
+ /** Имена всех дырок текста — по ним `doctor` говорит, чего проекту не хватает. */
29
+ export function placeholdersOf(text) {
30
+ const names = [];
31
+ for (const [, name] of text.matchAll(PLACEHOLDER)) {
32
+ if (!names.includes(name)) {
33
+ names.push(name);
34
+ }
35
+ }
36
+ return names;
37
+ }
38
+ //# sourceMappingURL=vars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vars.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/vars.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,GAAW,4BAA4B,CAAC;AAQzD,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,IAAsC;IAC3E,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,MAAM,QAAQ,GAAW,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAa,EAAE,IAAY,EAAU,EAAE;QACvF,MAAM,KAAK,GAAuB,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,CAAC;YAED,OAAO,KAAK,CAAC;QACjB,CAAC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AACvC,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,IAAY;IACvC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["/**\n * Подстановка значений проекта в текст пакета.\n *\n * Пакет не знает ни префикса компонентов, ни имени главной ветки, ни портов стендов, но говорить\n * о них обязан: правило без этих имён перестаёт быть указанием и превращается в пожелание.\n * Поэтому в тексте пакета стоят дырки `{{имя}}`, а значения приходят из конфига проекта.\n *\n * Неподставленная дырка — **отказ**, а не строка в тексте. Разложенный файл читает агент, и\n * `{{componentPrefix}}` посреди правила он прочтёт как имя: правило станет неверным молча, и\n * заметит это не проверка, а тот, кто будет разбирать последствия.\n */\n\n/** Дырка в тексте пакета: `{{имя}}`, имя — латиница, цифры, дефис и точка. */\nconst PLACEHOLDER: RegExp = /\\{\\{([a-zA-Z][\\w.-]*)\\}\\}/g;\n\nexport interface IRenderResult {\n readonly text: string;\n /** Имена дырок, которым не нашлось значения, в порядке первого появления. */\n readonly missing: readonly string[];\n}\n\nexport function renderVars(text: string, vars: Readonly<Record<string, string>>): IRenderResult {\n const missing: string[] = [];\n\n const rendered: string = text.replace(PLACEHOLDER, (whole: string, name: string): string => {\n const value: string | undefined = vars[name];\n if (value === undefined) {\n if (!missing.includes(name)) {\n missing.push(name);\n }\n\n return whole;\n }\n\n return value;\n });\n\n return { text: rendered, missing };\n}\n\n/** Имена всех дырок текста — по ним `doctor` говорит, чего проекту не хватает. */\nexport function placeholdersOf(text: string): readonly string[] {\n const names: string[] = [];\n for (const [, name] of text.matchAll(PLACEHOLDER)) {\n if (!names.includes(name)) {\n names.push(name);\n }\n }\n\n return names;\n}\n"]}