@nitra/cursor 1.1.0 → 1.2.1

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.
@@ -0,0 +1,35 @@
1
+ # AGENTS.md version: '1.0'
2
+
3
+ ## Purpose
4
+
5
+ This file is the entry point for all AI agents working with this repository.
6
+
7
+ ## Rule source
8
+
9
+ The primary development rules are stored in the Cursor rules directory:
10
+
11
+ {{#services}}
12
+ {{name}}
13
+ {{/services}}
14
+
15
+ ## Instructions for all agents
16
+
17
+ Before making changes, read the relevant rule files for the area you are working on.
18
+
19
+ ## Priority
20
+
21
+ If rules conflict:
22
+
23
+ 1. AGENTS.md
24
+ 2. task-specific rule file
25
+ 3. core rule file
26
+
27
+ ## Language
28
+
29
+ Respond in Ukrainian.
30
+ Keep technical terms in English.
31
+
32
+ ## Behavior
33
+
34
+ Do not ignore referenced rule files.
35
+ Explicitly follow repository conventions before proposing or applying changes.
package/README.md CHANGED
@@ -14,11 +14,7 @@
14
14
 
15
15
  ```json
16
16
  {
17
- "rules": [
18
- "js-format",
19
- "npm-module",
20
- "spell"
21
- ]
17
+ "rules": ["js-format", "npm-module", "spell"]
22
18
  }
23
19
  ```
24
20
 
@@ -47,10 +43,10 @@ npx @nitra/cursor
47
43
 
48
44
  CLI автоматично:
49
45
 
50
- 1. Знайде `nitra-cursor.json` у поточній директорії
51
- 2. Завантажить кожне з перелічених правил з unpkg.com
52
- 3. Створить директорію `.cursor/rules/` якщо вона відсутня
53
- 4. Збереже файли з префіксом `nitra-`
46
+ 1. Знайде або створить `nitra-cursor.json` у поточній директорії
47
+ 2. Створить директорію `.cursor/rules/`, якщо її ще немає
48
+ 3. Завантажить кожне з перелічених у конфігу правило з unpkg.com і збереже файли з префіксом `nitra-`
49
+ 4. Після оновлення файлів на диску згенерує в корені проєкту **`AGENTS.md`**: повний вміст береться з шаблону пакету `AGENTS.template.md`, а список правил у шаблоні формується з **усіх наявних файлів `*.mdc`** у `.cursor/rules/` (відсортовано за ім’ям)
54
50
 
55
51
  ## Приклад виводу
56
52
 
@@ -61,6 +57,7 @@ CLI автоматично:
61
57
  ⬇ js-format → .cursor/rules/nitra-js-format.mdc ... ✅
62
58
  ⬇ npm-module → .cursor/rules/nitra-npm-module.mdc ... ✅
63
59
  ⬇ spell → .cursor/rules/nitra-spell.mdc ... ✅
60
+ 📝 Оновлено AGENTS.md з AGENTS.template.md
64
61
 
65
62
  ✨ Готово: 3 завантажено, 0 з помилками
66
63
  ```
@@ -69,7 +66,8 @@ CLI автоматично:
69
66
 
70
67
  ```
71
68
  npm/
72
- ├── mdc/ # cursor-правила
69
+ ├── AGENTS.template.md # шаблон AGENTS.md для цільових репозиторіїв (потрапляє в npm-архів)
70
+ ├── mdc/ # cursor-правила
73
71
  │ ├── js-format.mdc
74
72
  │ ├── npm-module.mdc
75
73
  │ └── spell.mdc
@@ -77,6 +75,37 @@ npm/
77
75
  └── nitra-cursor.js # CLI-скрипт
78
76
  ```
79
77
 
78
+ ## AGENTS.md у проєкті користувача
79
+
80
+ Після кожного успішного проходу завантаження правил CLI **повністю перезаписує** файл **`AGENTS.md`** у корені поточної директорії (та сама директорія, де лежить `nitra-cursor.json`).
81
+
82
+ - **Джерело тексту** — файл **`AGENTS.template.md`** з установленого пакету `@nitra/cursor` (його не редагують у чужому репозиторії; зміни вносять у цьому репозиторії пакету).
83
+ - **Динамічний список правил** - Скрипт зчитує каталог **`.cursor/rules/`** і для **кожного файлу з розширенням `.mdc`** додає в шаблон рядок виду `- .cursor/rules/<ім’я>.mdc`. Туди потрапляють і правила Nitra з префіксом `nitra-`, і будь-які інші `.mdc`, які вже лежать у цій папці.
84
+ - Редагувати згенерований **`AGENTS.md` у проєкті користувача немає сенсу** — наступний запуск CLI знову замінить файл. Власні інструкції для агентів треба закладати в **`AGENTS.template.md`** у репозиторії `@nitra/cursor` або тримати окремо від автогенерації.
85
+
86
+ ## Інструкція для розробників пакету
87
+
88
+ ### Зміна шаблону AGENTS
89
+
90
+ 1. Редагуйте **`npm/AGENTS.template.md`**. Файл має бути перелічений у полі **`files`** у `npm/package.json`, щоб потрапляти в публікацію npm (разом з `mdc/` та `bin/`).
91
+ 2. Для вставки списку файлів правил використовуйте блок у стилі Mustache з ім’ям секції **`services`** і плейсхолдером **`{{name}}`**:
92
+
93
+ ```markdown
94
+ {{#services}}
95
+ {{name}}
96
+ {{/services}}
97
+ ```
98
+
99
+ Під час запуску CLI тіло між `{{#services}}` і `{{/services}}` повторюється для кожного `*.mdc` у `.cursor/rules/`; у `{{name}}` підставляється вже готовий markdown-рядок (наприклад `- .cursor/rules/nitra-spell.mdc`).
100
+
101
+ 3. Після змін у шаблоні перевірте локально: у тестовому репозиторії з `nitra-cursor.json` виконайте `npx`/`bunx` на зібраному пакеті або `node npm/bin/nitra-cursor.js` з кореня того репозиторію і переконайтеся, що **`AGENTS.md`** виглядає як очікується.
102
+
103
+ ### Логіка в коді CLI
104
+
105
+ - Шлях до шаблону: поруч із `mdc/`, тобто `…/node_modules/@nitra/cursor/AGENTS.template.md` після встановлення пакету.
106
+ - Оновлення **`AGENTS.md`** виконується **після** циклу завантаження правил, щоб список відображав актуальний вміст `.cursor/rules/` на диску.
107
+ - Якщо каталогу `.cursor/rules/` немає або в ньому немає `*.mdc`, блок `{{#services}}` стає порожнім; решта шаблону все одно записується в **`AGENTS.md`**.
108
+
80
109
  ## Мета проекту
81
110
 
82
111
  Консольна утиліта яка дозволить оновлювати в локальних GIT репозиторіях правила для cursor з можливістю наслідування правил від файлів в цьому репозиторії та забезпечення версійності правил для cursor.
@@ -10,6 +10,9 @@
10
10
  *
11
11
  * Якщо у корені репозиторію немає nitra-cursor.json, він створюється автоматично
12
12
  * з усіма правилами з каталогу mdc пакету (їх можна відредагувати після створення).
13
+ *
14
+ * Файл AGENTS.md у корені: щоразу повністю перезаписується змістом з AGENTS.template.md
15
+ * пакету; список правил у шаблоні будується з файлів *.mdc у .cursor/rules поточного проєкту.
13
16
  */
14
17
 
15
18
  import { existsSync } from 'node:fs'
@@ -21,11 +24,14 @@ import { fileURLToPath } from 'node:url'
21
24
  const PACKAGE_NAME = '@nitra/cursor'
22
25
  const UNPKG_BASE = 'https://unpkg.com'
23
26
  const CONFIG_FILE = 'nitra-cursor.json'
27
+ const AGENTS_FILE = 'AGENTS.md'
28
+ const AGENTS_TEMPLATE_FILE = 'AGENTS.template.md'
24
29
  const RULES_DIR = '.cursor/rules'
25
30
  const RULE_PREFIX = 'nitra-'
26
31
 
27
32
  const binDir = dirname(fileURLToPath(import.meta.url))
28
33
  const BUNDLED_MDC_DIR = join(binDir, '..', 'mdc')
34
+ const BUNDLED_AGENTS_TEMPLATE_PATH = join(binDir, '..', AGENTS_TEMPLATE_FILE)
29
35
 
30
36
  /**
31
37
  * Імена правил (без .mdc) з каталогу mdc поточної інсталяції пакету
@@ -74,7 +80,7 @@ async function readConfig() {
74
80
  const defaultConfig = { rules }
75
81
  await writeFile(configPath, `${JSON.stringify(defaultConfig, null, 2)}\n`, 'utf8')
76
82
  console.log(
77
- `📝 Створено ${CONFIG_FILE} з усіма правилами з пакету (${rules.length}). ` + `За потреби відредагуйте список.\n`
83
+ `📝 Створено ${CONFIG_FILE} з усіма правилами з пакету (${rules.length}). За потреби відредагуйте список.\n`
78
84
  )
79
85
  return defaultConfig
80
86
  }
@@ -115,6 +121,79 @@ function normalizeRuleName(ruleName) {
115
121
  return basename(name)
116
122
  }
117
123
 
124
+ /**
125
+ * Розгортає в шаблоні блок Mustache {{#section}} … {{/section}} для масиву елементів
126
+ * @param {string} template вихідний текст шаблону
127
+ * @param {string} section ім'я секції (наприклад services)
128
+ * @param {Record<string, string>[]} items елементи для повторення тіла секції
129
+ * @param {string} prop ключ поля для підстановки замість {{prop}}
130
+ * @returns {string} текст після розгортання усіх входжень блоку
131
+ */
132
+ function expandMustacheSection(template, section, items, prop) {
133
+ const open = `{{#${section}}}`
134
+ const close = `{{/${section}}}`
135
+ const placeholder = `{{${prop}}}`
136
+ let result = template
137
+ let start = result.indexOf(open)
138
+ let end = result.indexOf(close)
139
+ while (start !== -1 && end !== -1 && end > start) {
140
+ const inner = result.slice(start + open.length, end)
141
+ const rendered = items.map(item => inner.split(placeholder).join(String(item[prop]))).join('')
142
+ result = result.slice(0, start) + rendered + result.slice(end + close.length)
143
+ start = result.indexOf(open)
144
+ end = result.indexOf(close)
145
+ }
146
+ return result
147
+ }
148
+
149
+ /**
150
+ * Підставляє у вміст AGENTS.template.md список шляхів до файлів правил
151
+ * @param {string} templateText вміст AGENTS.template.md
152
+ * @param {string[]} mdcBasenames імена файлів (*.mdc) з .cursor/rules
153
+ * @returns {string} готовий markdown для AGENTS.md
154
+ */
155
+ function renderAgentsTemplate(templateText, mdcBasenames) {
156
+ const items = mdcBasenames.map(mdcName => ({
157
+ name: `- ${RULES_DIR}/${mdcName}`
158
+ }))
159
+ return expandMustacheSection(templateText, 'services', items, 'name')
160
+ }
161
+
162
+ /**
163
+ * Повертає відсортовані імена *.mdc у .cursor/rules поточного проєкту
164
+ * @returns {Promise<string[]>} базові імена файлів (лише .mdc)
165
+ */
166
+ async function listProjectRulesMdcFiles() {
167
+ const rulesDir = join(cwd(), RULES_DIR)
168
+ if (!existsSync(rulesDir)) {
169
+ return []
170
+ }
171
+ const names = await readdir(rulesDir)
172
+ return names.filter(n => n.endsWith('.mdc')).sort((a, b) => a.localeCompare(b))
173
+ }
174
+
175
+ /**
176
+ * Повністю перезаписує AGENTS.md у корені cwd з npm/AGENTS.template.md
177
+ * @returns {Promise<void>} завершення запису файлу
178
+ */
179
+ async function syncAgentsMd() {
180
+ if (!existsSync(BUNDLED_AGENTS_TEMPLATE_PATH)) {
181
+ throw new Error(
182
+ `Не знайдено шаблон ${AGENTS_TEMPLATE_FILE} у пакеті.\n` +
183
+ `Очікуваний шлях: ${BUNDLED_AGENTS_TEMPLATE_PATH}\n` +
184
+ `Перевстановіть ${PACKAGE_NAME}.`
185
+ )
186
+ }
187
+ const templateText = await readFile(BUNDLED_AGENTS_TEMPLATE_PATH, 'utf8')
188
+ const mdcFiles = await listProjectRulesMdcFiles()
189
+ const body = renderAgentsTemplate(templateText, mdcFiles)
190
+ const agentsPath = join(cwd(), AGENTS_FILE)
191
+ const hadFile = existsSync(agentsPath)
192
+ const out = body.endsWith('\n') ? body : `${body}\n`
193
+ await writeFile(agentsPath, out, 'utf8')
194
+ console.log(hadFile ? `📝 Оновлено ${AGENTS_FILE} з ${AGENTS_TEMPLATE_FILE}` : `📝 Створено ${AGENTS_FILE} з ${AGENTS_TEMPLATE_FILE}`)
195
+ }
196
+
118
197
  console.log(`\n🔧 @nitra/cursor — завантаження cursor-правил\n`)
119
198
 
120
199
  // 1. Зчитуємо конфіг
@@ -158,7 +237,15 @@ for (const rule of rules) {
158
237
  }
159
238
  }
160
239
 
161
- // 4. Підсумок
240
+ // 4. AGENTS.md зі списком файлів *.mdc у .cursor/rules (після оновлення на диску)
241
+ try {
242
+ await syncAgentsMd()
243
+ } catch (error) {
244
+ console.error(`❌ Не вдалося оновити ${AGENTS_FILE}: ${error.message}`)
245
+ process.exit(1)
246
+ }
247
+
248
+ // 5. Підсумок
162
249
  console.log(`\n✨ Готово: ${successCount} завантажено, ${failCount} з помилками\n`)
163
250
  if (failCount > 0) {
164
251
  process.exit(1)
@@ -0,0 +1,108 @@
1
+ ---
2
+ description: Правила nginx для статичних файлів
3
+ version: '1.0'
4
+ ---
5
+
6
+ # Якщо в проекті є файл default.tpl.conf або default.conf.template
7
+
8
+ Якщо файл називається default.tpl.conf його потрібно перейменувати на default.conf.template
9
+
10
+ default.conf.template повинен виглядати так:
11
+
12
+ ```nginx
13
+ server_tokens off;
14
+ port_in_redirect off;
15
+ client_max_body_size 0;
16
+ client_body_buffer_size 512M;
17
+
18
+ server {
19
+ listen 8080;
20
+ server_name _;
21
+
22
+ # disable all log
23
+ access_log off;
24
+ error_log off;
25
+
26
+ # This would be the directory where your Vite app's static files are stored at
27
+ root /usr/share/nginx/html;
28
+
29
+ location /healthz {
30
+ add_header Content-Type text/plain;
31
+ access_log off;
32
+ return 200 "healthy";
33
+ }
34
+
35
+ # Без gz стиснених файлів, 1 year is 31536000 seconds
36
+ location ~ ^$PUBLIC_PATH/(.+\.(?:gif|jpe?g|png|ico|woff2|xlsx))$ {
37
+ alias /usr/share/nginx/html/$1;
38
+ add_header 'Cache-Control' "public,max-age=31536000,immutable";
39
+ }
40
+
41
+ # С gz стисненими файлами, 1 year is 31536000 seconds
42
+ location ~ ^$PUBLIC_PATH/(.+\.(?:svg|js|css|ttf|map|xml|webmanifest|wasm))$ {
43
+ alias /usr/share/nginx/html/$1;
44
+ add_header 'Cache-Control' "public,max-age=31536000,immutable";
45
+
46
+ # дозволяє віддавати замість звичайного файлу попередньо стиснутий файл з таким же ім'ям та з розширенням ".gz"
47
+ gzip_static on;
48
+ }
49
+
50
+ location $PUBLIC_PATH/ {
51
+ index index.html;
52
+ alias /usr/share/nginx/html/;
53
+
54
+ # eliminates the step of copying the data into the buffer and enables direct copying data from one file descriptor to another.
55
+ sendfile on;
56
+ # to prevent one fast connection from entirely occupying the worker process
57
+ sendfile_max_chunk 512k;
58
+ # to send HTTP response headers in one packet right after the chunk of data has been obtained by sendfile().
59
+ tcp_nopush on;
60
+
61
+ # дозволяє віддавати замість звичайного файлу попередньо стиснутий файл з таким же ім'ям та з розширенням ".gz"
62
+ gzip_static on;
63
+
64
+ try_files $uri $uri/ /index.html =404;
65
+ }
66
+ }
67
+ ```
68
+
69
+ Тобто в ньому не потрібно щоб було жодного proxy_pass секціі і інших опцій для проxy.
70
+
71
+ Якщо в файлі вже були proxy_pass секції, то їх логіку потрібно перенести до HTTPRoute в k8s.
72
+
73
+ В Dockerfile потрібно додати команду для стиснення файлів, які потім використовуватимуться з `gzip_static on`:
74
+
75
+ ```dockerfile
76
+ RUN for s in js css map xml webmanifest html wasm; do find /usr/share/nginx/html -type f -name "*.$$s" -exec gzip -k {} +; done
77
+ ```
78
+
79
+ Поруч з файлом default.conf.template повинні бути конфігураційні файли *.ini для різних середовищ. В Dockerfile повинна бути команда, для заміни плейсхолдерів в цих файлах на значення з середовища:
80
+
81
+ ```dockerfile
82
+ # 1) Витягнути імена змінних з ini (ігноруємо коментарі/порожні)
83
+ # 2) Зробити список для envsubst: $NAMESPACE $NAMESPACE2 ...
84
+ # 3) Підвантажити значення з ini і підставити лише їх
85
+ RUN NAMES=$(sed -nE '/^\s*[#;]/d; /^\s*$/d; s/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=.*/\1/p' /tpl/values-$BRANCH.ini) && \
86
+ VARS=$(printf '%s\n' $NAMES | awk '{printf "$%s ", $0}') && \
87
+ export $(grep -v '^#' /tpl/values-$BRANCH.ini | xargs) && \
88
+ envsubst "$VARS" < /tpl/default.conf.template > /app/default.conf
89
+ ```
90
+
91
+ в файлі .vscode/extensions.json є налаштування для oxfmt:
92
+
93
+ ```json title=".vscode/extensions.json"
94
+ {
95
+ "recommendations": ["ahmadalli.vscode-nginx-conf"]
96
+ }
97
+ ```
98
+
99
+ в файлі .vscode/settings.json є налаштування для oxfmt:
100
+
101
+ ```json title=".vscode/settings.json"
102
+ {
103
+ "editor.formatOnSave": true,
104
+ "[nginx]": {
105
+ "editor.defaultFormatter": "ahmadalli.vscode-nginx-conf"
106
+ }
107
+ }
108
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "CLI для завантаження cursor-правил Nitra у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "files": [
26
26
  "mdc",
27
- "bin"
27
+ "bin",
28
+ "AGENTS.template.md"
28
29
  ],
29
30
  "type": "module",
30
31
  "scripts": {