@rt-tools/agent-kit 0.1.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 (131) hide show
  1. package/README.md +88 -9
  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/laws/access.md +3 -12
  22. package/assets/laws/admin-lists.md +9 -21
  23. package/assets/laws/admin-navigation.md +3 -15
  24. package/assets/laws/code-structure.md +5 -16
  25. package/assets/laws/delivery.md +2 -16
  26. package/assets/laws/entity-editing.md +7 -20
  27. package/assets/laws/entity-models.md +10 -17
  28. package/assets/laws/frontend-application.md +3 -14
  29. package/assets/laws/lib-imports.md +0 -13
  30. package/assets/laws/locales.md +2 -11
  31. package/assets/laws/project-documentation.md +7 -20
  32. package/assets/laws/reuse-first.md +0 -9
  33. package/assets/laws/search-visibility.md +0 -13
  34. package/assets/laws/shared-code.md +0 -13
  35. package/assets/laws/verifiability.md +0 -13
  36. package/assets/patterns/angular-patterns-state.md +94 -0
  37. package/assets/patterns/api-layer-pair.md +78 -0
  38. package/assets/patterns/browser-verification-measure.md +83 -0
  39. package/assets/patterns/browser-verification-stand.md +79 -0
  40. package/assets/patterns/component-structure-new.md +98 -0
  41. package/assets/patterns/doc-style-sweep.md +100 -0
  42. package/assets/patterns/doc-style-write.md +106 -0
  43. package/assets/patterns/git-workflow-commit.md +175 -0
  44. package/assets/patterns/git-workflow-merge.md +82 -0
  45. package/assets/patterns/git-workflow-migration.md +58 -0
  46. package/assets/patterns/git-workflow-restart.md +49 -0
  47. package/assets/patterns/lib-layers-move.md +77 -0
  48. package/assets/patterns/lib-layers-new.md +70 -0
  49. package/assets/patterns/permissions-procedure.md +69 -0
  50. package/assets/patterns/platform-access-di.md +70 -0
  51. package/assets/patterns/reuse-first-extend.md +73 -0
  52. package/assets/patterns/seo-page.md +92 -0
  53. package/assets/patterns/seo-verify.md +64 -0
  54. package/assets/patterns/shared-code-new.md +80 -0
  55. package/assets/patterns/spec-driven-domain.md +100 -0
  56. package/assets/patterns/spec-driven-rule.md +112 -0
  57. package/assets/patterns/styling-bem-component.md +77 -0
  58. package/assets/patterns/styling-bem-layout.md +67 -0
  59. package/assets/patterns/testing-e2e.md +90 -0
  60. package/assets/patterns/testing-unit.md +93 -0
  61. package/assets/patterns/translations-key.md +51 -0
  62. package/assets/patterns/ts-procedure.md +66 -0
  63. package/assets/rules/angular-patterns.md +52 -0
  64. package/assets/rules/api-layer.md +53 -0
  65. package/assets/rules/browser-verification.md +69 -0
  66. package/assets/rules/component-structure.md +48 -0
  67. package/assets/rules/doc-style.md +61 -0
  68. package/assets/rules/git-workflow.md +106 -0
  69. package/assets/rules/lib-layers.md +54 -0
  70. package/assets/rules/permissions.md +52 -0
  71. package/assets/rules/platform-access.md +49 -0
  72. package/assets/rules/reuse-first.md +69 -0
  73. package/assets/rules/seo.md +50 -0
  74. package/assets/rules/shared-code.md +45 -0
  75. package/assets/rules/spec-driven.md +89 -0
  76. package/assets/rules/styling-bem.md +59 -0
  77. package/assets/rules/testing.md +69 -0
  78. package/assets/rules/translations.md +52 -0
  79. package/assets/rules/typescript-conventions.md +46 -0
  80. package/assets/templates/gate-map.sh +37 -0
  81. package/assets/templates/implementation.md +38 -0
  82. package/assets/templates/pattern.md +4 -0
  83. package/assets/templates/project.sh +41 -0
  84. package/assets/templates/rule.md +12 -23
  85. package/bin/agent-kit.d.ts +1 -1
  86. package/bin/agent-kit.d.ts.map +1 -1
  87. package/bin/agent-kit.js +63 -7
  88. package/bin/agent-kit.js.map +1 -1
  89. package/bin/prompt.d.ts +9 -0
  90. package/bin/prompt.d.ts.map +1 -0
  91. package/bin/prompt.js +57 -0
  92. package/bin/prompt.js.map +1 -0
  93. package/index.d.ts +2 -0
  94. package/index.d.ts.map +1 -1
  95. package/index.js +2 -0
  96. package/index.js.map +1 -1
  97. package/lib/assets.d.ts +10 -2
  98. package/lib/assets.d.ts.map +1 -1
  99. package/lib/assets.js +24 -28
  100. package/lib/assets.js.map +1 -1
  101. package/lib/catalog.d.ts +44 -0
  102. package/lib/catalog.d.ts.map +1 -0
  103. package/lib/catalog.js +86 -0
  104. package/lib/catalog.js.map +1 -0
  105. package/lib/commands.d.ts +13 -1
  106. package/lib/commands.d.ts.map +1 -1
  107. package/lib/commands.js +106 -11
  108. package/lib/commands.js.map +1 -1
  109. package/lib/companion.d.ts +53 -0
  110. package/lib/companion.d.ts.map +1 -0
  111. package/lib/companion.js +33 -0
  112. package/lib/companion.js.map +1 -0
  113. package/lib/config.d.ts +29 -1
  114. package/lib/config.d.ts.map +1 -1
  115. package/lib/config.js +43 -6
  116. package/lib/config.js.map +1 -1
  117. package/lib/picker.d.ts +47 -0
  118. package/lib/picker.d.ts.map +1 -0
  119. package/lib/picker.js +112 -0
  120. package/lib/picker.js.map +1 -0
  121. package/lib/stamp.d.ts +2 -5
  122. package/lib/stamp.d.ts.map +1 -1
  123. package/lib/stamp.js +25 -10
  124. package/lib/stamp.js.map +1 -1
  125. package/lib/sync.d.ts +3 -0
  126. package/lib/sync.d.ts.map +1 -1
  127. package/lib/sync.js +20 -1
  128. package/lib/sync.js.map +1 -1
  129. package/package.json +1 -1
  130. package/rt-tools-agent-kit-0.3.0.tgz +0 -0
  131. package/rt-tools-agent-kit-0.1.0.tgz +0 -0
package/lib/picker.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Выбиралка галочками — её состояние и разметка, без единого касания терминала.
3
+ *
4
+ * Отделено от ввода-вывода по той же причине, что и команды: нажатие клавиши — это переход
5
+ * состояния, и проверять его надо спекой, а не глазами на живом терминале. Сырой режим,
6
+ * перерисовка и восстановление курсора живут на краю, в `bin/prompt.ts`, и не решают ничего.
7
+ */
8
+ /** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */
9
+ export function pickerOf(choices) {
10
+ return { choices, cursor: 0, chosen: new Set(choices.map((choice) => choice.id)), done: false, cancelled: false };
11
+ }
12
+ /**
13
+ * Клавиши сырого потока. Записаны кодами, а не знаками: управляющий знак посреди исходника
14
+ * невидим, и правка рядом с ним ломает разбор молча.
15
+ *
16
+ * Стрелка приходит целиком, тремя знаками, — сравнивать надо всю последовательность.
17
+ */
18
+ const ESCAPE = String.fromCharCode(27);
19
+ export const KEY_UP = `${ESCAPE}[A`;
20
+ export const KEY_DOWN = `${ESCAPE}[B`;
21
+ export const KEY_ESCAPE = ESCAPE;
22
+ export const KEY_CANCEL = String.fromCharCode(3);
23
+ const UP = [KEY_UP, 'k'];
24
+ const DOWN = [KEY_DOWN, 'j'];
25
+ const DONE = ['\r', '\n'];
26
+ /** Ctrl-C и escape. Оба — «я передумал», и оба обязаны уйти без записи. */
27
+ const CANCEL = [KEY_CANCEL, KEY_ESCAPE];
28
+ function moved(state, step) {
29
+ const count = state.choices.length;
30
+ return count ? { ...state, cursor: (state.cursor + step + count) % count } : state;
31
+ }
32
+ function toggled(state) {
33
+ const choice = state.choices[state.cursor];
34
+ if (!choice) {
35
+ return state;
36
+ }
37
+ const chosen = new Set(state.chosen);
38
+ if (!chosen.delete(choice.id)) {
39
+ chosen.add(choice.id);
40
+ }
41
+ return { ...state, chosen };
42
+ }
43
+ function switched(state) {
44
+ const all = state.chosen.size === state.choices.length;
45
+ return { ...state, chosen: all ? new Set() : new Set(state.choices.map((choice) => choice.id)) };
46
+ }
47
+ /**
48
+ * Кусок сырого потока, разобранный на клавиши.
49
+ *
50
+ * Терминал не обещает по куску на нажатие: набранное быстро, вставленное из буфера и всё, что
51
+ * пришло по конвейеру, приезжает одной строкой. Неразобранный кусок читался бы как одна
52
+ * незнакомая клавиша, и выбиралка молча не отзывалась бы ни на что — так она и повисла на
53
+ * первом прогоне через конвейер.
54
+ *
55
+ * Escape-последовательность берётся целиком: `ESC [ A` — это стрелка, а не escape и две буквы.
56
+ */
57
+ export function keysOf(chunk) {
58
+ const points = Array.from(chunk);
59
+ const keys = [];
60
+ for (let index = 0; index < points.length; index++) {
61
+ if (points[index] === ESCAPE && points[index + 1] === '[' && points[index + 2] !== undefined) {
62
+ keys.push(points.slice(index, index + 3).join(''));
63
+ index += 2;
64
+ }
65
+ else {
66
+ keys.push(points[index]);
67
+ }
68
+ }
69
+ return keys;
70
+ }
71
+ /** Переход состояния по одной нажатой клавише. Незнакомая клавиша не делает ничего. */
72
+ export function pressKey(state, key) {
73
+ if (state.done || state.cancelled) {
74
+ return state;
75
+ }
76
+ if (UP.includes(key)) {
77
+ return moved(state, -1);
78
+ }
79
+ if (DOWN.includes(key)) {
80
+ return moved(state, 1);
81
+ }
82
+ if (CANCEL.includes(key)) {
83
+ return { ...state, cancelled: true };
84
+ }
85
+ if (DONE.includes(key)) {
86
+ return { ...state, done: true };
87
+ }
88
+ if (key === ' ') {
89
+ return toggled(state);
90
+ }
91
+ if (key === 'a') {
92
+ return switched(state);
93
+ }
94
+ return state;
95
+ }
96
+ /** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */
97
+ export const pressChunk = (state, chunk) => keysOf(chunk).reduce(pressKey, state);
98
+ const widest = (choices) => choices.reduce((width, choice) => Math.max(width, choice.name.length), 0);
99
+ /** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком в скобках. */
100
+ export function renderPicker(state, question) {
101
+ const width = widest(state.choices);
102
+ return [
103
+ question,
104
+ ' ↑↓ — двигаться, пробел — выбрать, a — все или ни одного, enter — готово',
105
+ '',
106
+ ...state.choices.map((choice, index) => {
107
+ const mark = state.chosen.has(choice.id) ? 'x' : ' ';
108
+ return `${index === state.cursor ? '❯' : ' '} [${mark}] ${choice.name.padEnd(width)} ${choice.title}`;
109
+ }),
110
+ ];
111
+ }
112
+ //# sourceMappingURL=picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picker.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/picker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Выбиралка галочками — её состояние и разметка, без единого касания терминала.\n *\n * Отделено от ввода-вывода по той же причине, что и команды: нажатие клавиши — это переход\n * состояния, и проверять его надо спекой, а не глазами на живом терминале. Сырой режим,\n * перерисовка и восстановление курсора живут на краю, в `bin/prompt.ts`, и не решают ничего.\n */\n\nexport interface IChoice {\n readonly id: string;\n /** Имя, которым ресурс называют в строке запуска: `access`. */\n readonly name: string;\n /** Заголовок ресурса — им человек и выбирает, имена ему ни о чём не говорят. */\n readonly title: string;\n}\n\nexport interface IPickerState {\n readonly choices: readonly IChoice[];\n readonly cursor: number;\n readonly chosen: ReadonlySet<string>;\n /** Выбор окончен, `chosen` — ответ. */\n readonly done: boolean;\n /** Выбор брошен: ответа нет, и подставлять вместо него умолчание нельзя. */\n readonly cancelled: boolean;\n}\n\n/** Первым предложением взято всё: отказ от закона — решение, и принимать его должен человек. */\nexport function pickerOf(choices: readonly IChoice[]): IPickerState {\n return { choices, cursor: 0, chosen: new Set(choices.map((choice: IChoice): string => choice.id)), done: false, cancelled: false };\n}\n\n/**\n * Клавиши сырого потока. Записаны кодами, а не знаками: управляющий знак посреди исходника\n * невидим, и правка рядом с ним ломает разбор молча.\n *\n * Стрелка приходит целиком, тремя знаками, — сравнивать надо всю последовательность.\n */\nconst ESCAPE: string = String.fromCharCode(27);\n\nexport const KEY_UP: string = `${ESCAPE}[A`;\nexport const KEY_DOWN: string = `${ESCAPE}[B`;\nexport const KEY_ESCAPE: string = ESCAPE;\nexport const KEY_CANCEL: string = String.fromCharCode(3);\n\nconst UP: readonly string[] = [KEY_UP, 'k'];\nconst DOWN: readonly string[] = [KEY_DOWN, 'j'];\nconst DONE: readonly string[] = ['\\r', '\\n'];\n/** Ctrl-C и escape. Оба — «я передумал», и оба обязаны уйти без записи. */\nconst CANCEL: readonly string[] = [KEY_CANCEL, KEY_ESCAPE];\n\nfunction moved(state: IPickerState, step: number): IPickerState {\n const count: number = state.choices.length;\n\n return count ? { ...state, cursor: (state.cursor + step + count) % count } : state;\n}\n\nfunction toggled(state: IPickerState): IPickerState {\n const choice: IChoice | undefined = state.choices[state.cursor];\n if (!choice) {\n return state;\n }\n const chosen: Set<string> = new Set(state.chosen);\n if (!chosen.delete(choice.id)) {\n chosen.add(choice.id);\n }\n\n return { ...state, chosen };\n}\n\nfunction switched(state: IPickerState): IPickerState {\n const all: boolean = state.chosen.size === state.choices.length;\n\n return { ...state, chosen: all ? new Set() : new Set(state.choices.map((choice: IChoice): string => choice.id)) };\n}\n\n/**\n * Кусок сырого потока, разобранный на клавиши.\n *\n * Терминал не обещает по куску на нажатие: набранное быстро, вставленное из буфера и всё, что\n * пришло по конвейеру, приезжает одной строкой. Неразобранный кусок читался бы как одна\n * незнакомая клавиша, и выбиралка молча не отзывалась бы ни на что — так она и повисла на\n * первом прогоне через конвейер.\n *\n * Escape-последовательность берётся целиком: `ESC [ A` — это стрелка, а не escape и две буквы.\n */\nexport function keysOf(chunk: string): readonly string[] {\n const points: readonly string[] = Array.from(chunk);\n const keys: string[] = [];\n\n for (let index: number = 0; index < points.length; index++) {\n if (points[index] === ESCAPE && points[index + 1] === '[' && points[index + 2] !== undefined) {\n keys.push(points.slice(index, index + 3).join(''));\n index += 2;\n } else {\n keys.push(points[index]);\n }\n }\n\n return keys;\n}\n\n/** Переход состояния по одной нажатой клавише. Незнакомая клавиша не делает ничего. */\nexport function pressKey(state: IPickerState, key: string): IPickerState {\n if (state.done || state.cancelled) {\n return state;\n }\n if (UP.includes(key)) {\n return moved(state, -1);\n }\n if (DOWN.includes(key)) {\n return moved(state, 1);\n }\n if (CANCEL.includes(key)) {\n return { ...state, cancelled: true };\n }\n if (DONE.includes(key)) {\n return { ...state, done: true };\n }\n if (key === ' ') {\n return toggled(state);\n }\n if (key === 'a') {\n return switched(state);\n }\n\n return state;\n}\n\n/** Кусок потока целиком: разобрать на клавиши и провести состояние через каждую. */\nexport const pressChunk: (state: IPickerState, chunk: string) => IPickerState = (state: IPickerState, chunk: string): IPickerState =>\n keysOf(chunk).reduce(pressKey, state);\n\nconst widest: (choices: readonly IChoice[]) => number = (choices: readonly IChoice[]): number =>\n choices.reduce((width: number, choice: IChoice): number => Math.max(width, choice.name.length), 0);\n\n/** Разметка выбиралки строками. Курсор — стрелкой, выбранное — крестиком в скобках. */\nexport function renderPicker(state: IPickerState, question: string): readonly string[] {\n const width: number = widest(state.choices);\n\n return [\n question,\n ' ↑↓ — двигаться, пробел — выбрать, a — все или ни одного, enter — готово',\n '',\n ...state.choices.map((choice: IChoice, index: number): string => {\n const mark: string = state.chosen.has(choice.id) ? 'x' : ' ';\n\n return `${index === state.cursor ? '❯' : ' '} [${mark}] ${choice.name.padEnd(width)} ${choice.title}`;\n }),\n ];\n}\n"]}
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":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAezC,4FAA4F;AAC5F,MAAM,CAAC,MAAM,QAAQ,GAA6B,CAAC,IAAY,EAAU,EAAE,CACvE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAYzE,MAAM,QAAQ,GAAkC,IAAI,GAAG,CAAmB;IACtE,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACxC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACjC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACnC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAClC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,SAAS,GAA+B,CAAC,IAAY,EAAY,EAAE,CACrE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAE3E,MAAM,UAAU,GAAW,kDAAkD,CAAC;AAE9E,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,IAAY;IACnD,MAAM,OAAO,GAAa,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAW,WAAW,KAAK,CAAC,OAAO,MAAM,KAAK,CAAC,KAAK,MAAM,KAAK,CAAC,IAAI,mCAAmC,CAAC;IAElH,OAAO,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACpC,MAAM,KAAK,GAAa,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,KAAK,GAAW,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACpG,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,KAAK,GAAqB,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,UAAU,CAAqB,CAAC;IACnF,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,GAAa,KAAK,CAAC;IACjD,MAAM,IAAI,GAAW,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEtF,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC;AACrD,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,KAAa,EAAE,IAAY;IAChE,MAAM,IAAI,GAAW,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9C,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,GAAG,GAAW,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAEvC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;IACtE,CAAC;IAED,OAAO,GAAG,IAAI,KAAK,IAAI,EAAE,CAAC;AAC9B,CAAC","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.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Переносимый слой правил для агента: законы, хуки, проверки и агенты, раскладываемые в репозиторий одной командой",
5
5
  "author": "RT Team",
6
6
  "license": "Apache-2.0",
Binary file
Binary file