@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
package/lib/config.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind = 'laws' | 'hooks' | 'checks' | 'agents' | 'templates';\n\nexport const KINDS: readonly TKind[] = ['laws', 'hooks', 'checks', 'agents', 'templates'];\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /**\n * Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.\n * Ограничивает только те роды, которые сам называет; как именно — `isChosen`.\n */\n readonly only: readonly string[];\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/money.md`. */\n readonly skip: readonly string[];\n}\n\nexport const CONFIG_PATH: string = '.claude/rt-kit.json';\nexport const OVERRIDES_DIR: string = '.claude/rt-kit/overrides';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n hooks: '.claude/hooks',\n checks: 'tools',\n agents: '.claude/agents',\n templates: '.claude/rt-kit/templates',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\nfunction idList(value: unknown, where: string): readonly string[] {\n const list: unknown = value ?? [];\n if (!Array.isArray(list) || list.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);\n }\n\n return list as string[];\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const only: readonly string[] = idList(raw['only'], 'only');\n const skip: readonly string[] = idList(raw['skip'], 'skip');\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n\n return { vars: { ...derived, ...stringMap(raw['vars'], 'vars') }, layout: layout as Record<TKind, string>, only, skip };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Конфиг проекта — `.claude/rt-kit.json`.\n *\n * Всё, что пакет не может знать сам: значения для дырок, куда класть каждый род ресурса и от\n * чего проект отказался. Конфиг лежит в дереве проекта и коммитится: раскладка обязана\n * повторяться на чужой машине без вопросов, иначе разложенное расходится между разработчиками.\n */\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/** Род ресурса. Он же имя каталога в `assets/` пакета и ключ раскладки. */\nexport type TKind = 'laws' | 'rules' | 'patterns' | 'hooks' | 'checks' | 'agents' | 'commands' | 'workflows' | 'templates';\n\nexport const KINDS: readonly TKind[] = ['laws', 'rules', 'patterns', 'hooks', 'checks', 'agents', 'commands', 'workflows', 'templates'];\n\n/**\n * Роды, которые ложатся не файлом в каталог, а каталогом по имени ресурса: скил читается как\n * `<имя>/SKILL.md`, и рядом с ним лежит то, что пишет проект.\n */\nexport const SKILL_KINDS: readonly TKind[] = ['rules', 'patterns'];\n\n/** Имя файла скила: так его ищет агент, и другого имени у него быть не может. */\nexport const SKILL_FILE: string = 'SKILL.md';\n\n/**\n * Файл-компаньон правила: чем названо в этом дереве то, о чём правило говорит приёмом.\n *\n * Пакет знает приём, но не знает ни путей, ни имён — их пишет проект. Черновик кладётся один\n * раз и дальше не сверяется: это единственный файл раскладки, который принадлежит проекту.\n */\nexport const COMPANION_FILE: string = 'implementation.md';\n\nexport interface IConfig {\n /** Значения дырок `{{имя}}`. */\n readonly vars: Readonly<Record<string, string>>;\n /** Куда класть каждый род ресурса, путями от корня проекта. */\n readonly layout: Readonly<Record<TKind, string>>;\n /**\n * Ресурсы, которые проект выбрал, идентификаторами: `laws/access.md`. Пусто — берётся всё.\n * Ограничивает только те роды, которые сам называет; как именно — `isChosen`.\n */\n readonly only: readonly string[];\n /** Ресурсы, от которых проект отказался, идентификаторами: `laws/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/** Имя дырки под путь карты гейта и её умолчание: карту пишет проект, путь знают оба. */\nexport const GATE_MAP_VAR: string = 'gateMap';\nexport const DEFAULT_GATE_MAP: string = '.claude/rt-kit/gate-map.sh';\n\n/**\n * Профиль проекта: команды, стенды и пары «правка — документ» этого дерева. Его читают\n * сторожевые хуки — механизм у них общий, а всё, что они зовут и называют, своё у каждого.\n */\nexport const PROFILE_VAR: string = 'projectProfile';\nexport const DEFAULT_PROFILE: string = '.claude/rt-kit/project.sh';\n\n/**\n * Умолчания раскладки. Это не единственно возможные пути, но менять их без нужды не стоит:\n * агент ищет законы в `docs/constitution`, потому что так написано в правилах, которые пакет\n * же и везёт.\n */\nexport const DEFAULT_LAYOUT: Readonly<Record<TKind, string>> = {\n laws: 'docs/constitution',\n rules: '.claude/skills',\n patterns: '.claude/skills',\n hooks: '.claude/hooks',\n checks: 'tools',\n agents: '.claude/agents',\n commands: '.claude/commands',\n workflows: '.claude/workflows',\n templates: '.claude/rt-kit/templates',\n};\n\nexport class ConfigError extends Error {}\n\nconst isRecord: (value: unknown) => value is Record<string, unknown> = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\nfunction stringMap(value: unknown, where: string): Record<string, string> {\n if (value === undefined) {\n return {};\n }\n if (!isRecord(value)) {\n throw new ConfigError(`${where}: ожидается объект «имя — значение»`);\n }\n const result: Record<string, string> = {};\n for (const [key, entry] of Object.entries(value)) {\n if (typeof entry !== 'string') {\n throw new ConfigError(`${where}.${key}: ожидается строка`);\n }\n result[key] = entry;\n }\n\n return result;\n}\n\nfunction idList(value: unknown, where: string): readonly string[] {\n const list: unknown = value ?? [];\n if (!Array.isArray(list) || list.some((entry: unknown): boolean => typeof entry !== 'string')) {\n throw new ConfigError(`${where}: ожидается список идентификаторов ресурсов`);\n }\n\n return list as string[];\n}\n\nexport function parseConfig(text: string): IConfig {\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error: unknown) {\n throw new ConfigError(`${CONFIG_PATH} — не разбирается как JSON: ${(error as Error).message}`);\n }\n if (!isRecord(raw)) {\n throw new ConfigError(`${CONFIG_PATH}: ожидается объект`);\n }\n\n const layout: Record<string, string> = { ...DEFAULT_LAYOUT, ...stringMap(raw['layout'], 'layout') };\n for (const kind of Object.keys(layout)) {\n if (!KINDS.includes(kind as TKind)) {\n throw new ConfigError(`layout.${kind}: такого рода ресурсов нет — есть ${KINDS.join(', ')}`);\n }\n }\n\n const only: readonly string[] = idList(raw['only'], 'only');\n const skip: readonly string[] = idList(raw['skip'], 'skip');\n\n // Пути раскладки — тоже значения проекта, и пакет их уже знает. Требовать их вторым списком\n // значило бы держать одно и то же в двух местах: расходятся такие пары молча.\n const derived: Record<string, string> = {};\n for (const kind of KINDS) {\n derived[`${kind.replace(/s$/, '')}sDir`] = layout[kind];\n }\n // Карту гейта пишет проект, но путь к ней знают обе стороны: хук её читает, шаблон\n // ложится рядом. Умолчание здесь избавляет проект от обязанности объявлять его самому —\n // а переопределить его он всё равно может, значением в `vars`.\n derived[GATE_MAP_VAR] = DEFAULT_GATE_MAP;\n derived[PROFILE_VAR] = DEFAULT_PROFILE;\n\n return { vars: { ...derived, ...stringMap(raw['vars'], 'vars') }, layout: layout as Record<TKind, string>, only, skip };\n}\n\n/** Конфиг проекта; его отсутствие — не отказ, а повод сказать про `agent-kit init`. */\nexport function readConfig(root: string): IConfig | null {\n try {\n return parseConfig(readFileSync(join(root, CONFIG_PATH), 'utf8'));\n } catch (error: unknown) {\n if (error instanceof ConfigError) {\n throw error;\n }\n\n return null;\n }\n}\n"]}
package/lib/stamp.d.ts CHANGED
@@ -12,11 +12,8 @@ export interface IStamped {
12
12
  /** Двенадцати шестнадцатеричных знаков хватает: шапку читает человек, а не сверяет сеть. */
13
13
  export declare const digestOf: (text: string) => string;
14
14
  export declare function formatStamp(stamp: IStamp, path: string): string;
15
- /**
16
- * Шапка и тело разложенного файла. Ищется она в первых двух строках: у скрипта первой стоит
17
- * `#!`, и требовать шапку строго первой значило бы ломать запуск.
18
- */
15
+ /** Шапка и тело разложенного файла. Ищется она в двух строках от начала тела. */
19
16
  export declare function readStamped(text: string): IStamped | null;
20
- /** Тело с шапкой: у скрипта она встаёт после строки запуска, у остальных — первой. */
17
+ /** Тело с шапкой: она встаёт после строки запуска и после вступления скила, иначе — первой. */
21
18
  export declare function applyStamp(body: string, stamp: IStamp, path: string): string;
22
19
  //# sourceMappingURL=stamp.d.ts.map
@@ -1 +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"}
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;AAsBD,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAczD;AAED,+FAA+F;AAC/F,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5E"}
package/lib/stamp.js CHANGED
@@ -31,27 +31,42 @@ export function formatStamp(stamp, path) {
31
31
  return `${comment.open}${text}${comment.close}`;
32
32
  }
33
33
  /**
34
- * Шапка и тело разложенного файла. Ищется она в первых двух строках: у скрипта первой стоит
35
- * `#!`, и требовать шапку строго первой значило бы ломать запуск.
34
+ * С какой строки начинается тело, поверх которого встаёт шапка.
35
+ *
36
+ * Первой строкой шапка встать может не всегда. У скрипта первой идёт `#!` — иначе он перестаёт
37
+ * запускаться. У скила первым идёт вступление между `---`, и оно обязано начинаться со строки
38
+ * ноль: строка перед ним превращает вступление в обычный текст, скил теряет имя и описание и
39
+ * перестаёт находиться вовсе — молча, потому что файл при этом остаётся читаемым.
36
40
  */
41
+ function bodyStart(lines) {
42
+ if (lines[0]?.startsWith('#!')) {
43
+ return 1;
44
+ }
45
+ if (lines[0]?.trim() !== '---') {
46
+ return 0;
47
+ }
48
+ const closing = lines.findIndex((line, index) => index > 0 && line.trim() === '---');
49
+ return closing < 0 ? 0 : closing + 1;
50
+ }
51
+ /** Шапка и тело разложенного файла. Ищется она в двух строках от начала тела. */
37
52
  export function readStamped(text) {
38
53
  const lines = text.split('\n');
39
- const index = lines.slice(0, 2).findIndex((line) => STAMP_LINE.test(line));
40
- if (index < 0) {
54
+ const from = bodyStart(lines);
55
+ const offset = lines.slice(from, from + 2).findIndex((line) => STAMP_LINE.test(line));
56
+ if (offset < 0) {
41
57
  return null;
42
58
  }
59
+ const index = from + offset;
43
60
  const found = lines[index].match(STAMP_LINE);
44
61
  const [, version, asset, hash] = found;
45
62
  const body = [...lines.slice(0, index), ...lines.slice(index + 1)].join('\n');
46
63
  return { stamp: { version, asset, hash }, body };
47
64
  }
48
- /** Тело с шапкой: у скрипта она встаёт после строки запуска, у остальных — первой. */
65
+ /** Тело с шапкой: она встаёт после строки запуска и после вступления скила, иначе — первой. */
49
66
  export function applyStamp(body, stamp, path) {
50
67
  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}`;
68
+ const lines = body.split('\n');
69
+ const from = bodyStart(lines);
70
+ return [...lines.slice(0, from), line, ...lines.slice(from)].join('\n');
56
71
  }
57
72
  //# sourceMappingURL=stamp.js.map
package/lib/stamp.js.map CHANGED
@@ -1 +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"]}
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 * Первой строкой шапка встать может не всегда. У скрипта первой идёт `#!` — иначе он перестаёт\n * запускаться. У скила первым идёт вступление между `---`, и оно обязано начинаться со строки\n * ноль: строка перед ним превращает вступление в обычный текст, скил теряет имя и описание и\n * перестаёт находиться вовсе — молча, потому что файл при этом остаётся читаемым.\n */\nfunction bodyStart(lines: readonly string[]): number {\n if (lines[0]?.startsWith('#!')) {\n return 1;\n }\n if (lines[0]?.trim() !== '---') {\n return 0;\n }\n const closing: number = lines.findIndex((line: string, index: number): boolean => index > 0 && line.trim() === '---');\n\n return closing < 0 ? 0 : closing + 1;\n}\n\n/** Шапка и тело разложенного файла. Ищется она в двух строках от начала тела. */\nexport function readStamped(text: string): IStamped | null {\n const lines: string[] = text.split('\\n');\n const from: number = bodyStart(lines);\n const offset: number = lines.slice(from, from + 2).findIndex((line: string): boolean => STAMP_LINE.test(line));\n if (offset < 0) {\n return null;\n }\n\n const index: number = from + offset;\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 const lines: string[] = body.split('\\n');\n const from: number = bodyStart(lines);\n\n return [...lines.slice(0, from), line, ...lines.slice(from)].join('\\n');\n}\n"]}
package/lib/sync.d.ts CHANGED
@@ -1,9 +1,12 @@
1
+ import { ICompanion } from './companion.js';
1
2
  import { IConfig } from './config.js';
2
3
  import { IPlanned } from './plan.js';
3
4
  export interface ISyncResult {
4
5
  readonly planned: readonly IPlanned[];
5
6
  /** Дырки без значения, по ресурсам. Непустой список — отказ: подставлять нечего. */
6
7
  readonly missing: ReadonlyMap<string, readonly string[]>;
8
+ /** Компаньоны разложенных правил: имена этого дерева, которые пишет проект. */
9
+ readonly companions: readonly ICompanion[];
7
10
  readonly written: readonly string[];
8
11
  }
9
12
  export declare function planSync(config: IConfig, root: string, version: string, assetsDir: string): ISyncResult;
package/lib/sync.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,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"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/sync.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,UAAU,EAAyB,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAkC,MAAM,WAAW,CAAC;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,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,SAAS,UAAU,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAoBD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAqBvG;AAED,mGAAmG;AACnG,wBAAgB,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CA8BtG;AAED,eAAO,MAAM,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,SAAS,QAAQ,EACe,CAAC"}
package/lib/sync.js CHANGED
@@ -8,10 +8,13 @@
8
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
9
  import { dirname, join } from 'node:path';
10
10
  import { collectAssets } from './assets.js';
11
+ import { pathOf, planCompanion } from './companion.js';
11
12
  import { OVERRIDES_DIR } from './config.js';
12
13
  import { isPending, isRefusal, planFile } from './plan.js';
13
14
  import { mergeDocuments, parseDocument, renderDocument } from './sections.js';
14
15
  import { renderVars } from './vars.js';
16
+ /** Шаблон черновика компаньона — тот же, что пакет кладёт проекту в шаблоны. */
17
+ const COMPANION_TEMPLATE = 'templates/implementation.md';
15
18
  const read = (path) => (existsSync(path) ? readFileSync(path, 'utf8') : null);
16
19
  /**
17
20
  * Тело ресурса: текст пакета, поверх него надстройка проекта, и уже потом подстановка значений.
@@ -27,6 +30,8 @@ function renderAsset(asset, config, root) {
27
30
  export function planSync(config, root, version, assetsDir) {
28
31
  const planned = [];
29
32
  const missing = new Map();
33
+ const companions = [];
34
+ const template = read(join(assetsDir, COMPANION_TEMPLATE));
30
35
  for (const asset of collectAssets(config, assetsDir)) {
31
36
  const rendered = renderAsset(asset, config, root);
32
37
  if (rendered.missing.length) {
@@ -34,8 +39,11 @@ export function planSync(config, root, version, assetsDir) {
34
39
  continue;
35
40
  }
36
41
  planned.push(planFile({ path: asset.target, asset: asset.id, version, rendered: rendered.text, existing: read(join(root, asset.target)) }));
42
+ if (asset.kind === 'rules' && template !== null) {
43
+ companions.push(planCompanion(asset, read(join(root, pathOf(asset))), template));
44
+ }
37
45
  }
38
- return { planned, missing, written: [] };
46
+ return { planned, missing, companions, written: [] };
39
47
  }
40
48
  /** Раскладка. Отказ хотя бы по одному файлу не пишет ничего: половина разложенного хуже целого. */
41
49
  export function runSync(config, root, version, assetsDir) {
@@ -53,6 +61,17 @@ export function runSync(config, root, version, assetsDir) {
53
61
  writeFileSync(path, entry.content, 'utf8');
54
62
  written.push(entry.path);
55
63
  }
64
+ // Черновик компаньона кладётся только там, где файла нет вовсе. Он без шапки и без суммы:
65
+ // сверять в нём нечего — с первой правки проекта это его текст, а не текст пакета.
66
+ for (const companion of result.companions) {
67
+ if (companion.content === null) {
68
+ continue;
69
+ }
70
+ const path = join(root, companion.path);
71
+ mkdirSync(dirname(path), { recursive: true });
72
+ writeFileSync(path, companion.content, 'utf8');
73
+ written.push(companion.path);
74
+ }
56
75
  return { ...result, written };
57
76
  }
58
77
  export const pendingOf = (result) => result.planned.filter((entry) => isPending(entry.outcome));
package/lib/sync.js.map CHANGED
@@ -1 +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"]}
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 { ICompanion, pathOf, planCompanion } from './companion.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 /** Компаньоны разложенных правил: имена этого дерева, которые пишет проект. */\n readonly companions: readonly ICompanion[];\n readonly written: readonly string[];\n}\n\n/** Шаблон черновика компаньона — тот же, что пакет кладёт проекту в шаблоны. */\nconst COMPANION_TEMPLATE: string = 'templates/implementation.md';\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 const companions: ICompanion[] = [];\n const template: string | null = read(join(assetsDir, COMPANION_TEMPLATE));\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 if (asset.kind === 'rules' && template !== null) {\n companions.push(planCompanion(asset, read(join(root, pathOf(asset))), template));\n }\n }\n\n return { planned, missing, companions, 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 // Черновик компаньона кладётся только там, где файла нет вовсе. Он без шапки и без суммы:\n // сверять в нём нечего — с первой правки проекта это его текст, а не текст пакета.\n for (const companion of result.companions) {\n if (companion.content === null) {\n continue;\n }\n const path: string = join(root, companion.path);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, companion.content, 'utf8');\n written.push(companion.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rt-tools/agent-kit",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Переносимый слой правил для агента: законы, хуки, проверки и агенты, раскладываемые в репозиторий одной командой",
5
5
  "author": "RT Team",
6
6
  "license": "Apache-2.0",
Binary file
Binary file