@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.
- package/AGENTS.template.md +35 -0
- package/README.md +39 -10
- package/bin/nitra-cursor.js +89 -2
- package/mdc/nginx-default-tpl.mdc +108 -0
- package/package.json +3 -2
|
@@ -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.
|
|
52
|
-
3.
|
|
53
|
-
4.
|
|
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
|
-
├──
|
|
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.
|
package/bin/nitra-cursor.js
CHANGED
|
@@ -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}).
|
|
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
|
|
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": {
|