@nitra/cursor 1.8.121 → 1.8.123
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/bin/n-cursor.js +25 -9
- package/mdc/abie.mdc +2 -0
- package/mdc/k8s.mdc +1 -1
- package/package.json +1 -1
- package/scripts/check-abie.mjs +141 -1
package/bin/n-cursor.js
CHANGED
|
@@ -384,6 +384,20 @@ function skillDescriptionSafeForMarkdownInline(desc) {
|
|
|
384
384
|
return desc.replaceAll('<id>', '{id}')
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
/**
|
|
388
|
+
* YAML frontmatter для `.claude/commands/*.md`: поле `description` потрібне розширенню VSCode,
|
|
389
|
+
* щоб команди з’являлись у списку. Текст збігається з полем `description` у frontmatter `SKILL.md`.
|
|
390
|
+
* @param {string} descriptionRaw значення з `extractSkillDescription` (може бути порожнім)
|
|
391
|
+
* @returns {string} блок `---` … `---` і порожній рядок після
|
|
392
|
+
*/
|
|
393
|
+
function formatClaudeCommandFrontmatter(descriptionRaw) {
|
|
394
|
+
let text = skillDescriptionSafeForMarkdownInline(String(descriptionRaw || '').trim())
|
|
395
|
+
if (!text) {
|
|
396
|
+
text = 'Див. SKILL.md у каталозі скілу в .cursor/skills.'
|
|
397
|
+
}
|
|
398
|
+
return `---\ndescription: >-\n ${text}\n---\n\n`
|
|
399
|
+
}
|
|
400
|
+
|
|
387
401
|
/**
|
|
388
402
|
* Розгортає в шаблоні блок Mustache {{#section}} … {{/section}} для масиву елементів
|
|
389
403
|
* @param {string} template вихідний текст шаблону
|
|
@@ -683,7 +697,8 @@ async function syncSkills(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
|
|
|
683
697
|
|
|
684
698
|
/**
|
|
685
699
|
* Синхронізує .claude/commands/n-<id>.md зі skills пакету.
|
|
686
|
-
*
|
|
700
|
+
* У кожному файлі обов’язково YAML frontmatter з `description` (як у `SKILL.md`), інакше команди
|
|
701
|
+
* не з’являються у розширенні VSCode; далі — заголовок H1 лише з імені команди (без повтору опису) і посилання на `.cursor/skills/…/SKILL.md`.
|
|
687
702
|
* @param {string[]} configSkills id без префікса n-
|
|
688
703
|
* @param {string} [bundledSkillsDir] каталог `skills/` у корені пакету-джерела
|
|
689
704
|
* @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
|
|
@@ -710,9 +725,9 @@ async function syncCommands(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR)
|
|
|
710
725
|
try {
|
|
711
726
|
const raw = await readFile(srcSkillMd, 'utf8')
|
|
712
727
|
const descRaw = extractSkillDescription(raw)
|
|
713
|
-
const
|
|
714
|
-
const header =
|
|
715
|
-
const body = `${header}Виконай інструкції зі скілу \`.cursor/skills/${destDirName}/SKILL.md\`.\n`
|
|
728
|
+
const frontmatter = formatClaudeCommandFrontmatter(descRaw || '')
|
|
729
|
+
const header = `# ${RULE_PREFIX}${id}\n\n`
|
|
730
|
+
const body = `${frontmatter}${header}Виконай інструкції зі скілу \`.cursor/skills/${destDirName}/SKILL.md\`.\n`
|
|
716
731
|
await writeFile(destFile, body, 'utf8')
|
|
717
732
|
console.log(`✅`)
|
|
718
733
|
success++
|
|
@@ -754,7 +769,7 @@ async function removeOrphanManagedCommandFiles(commandsDir, configSkills) {
|
|
|
754
769
|
|
|
755
770
|
/**
|
|
756
771
|
* Синхронізує .claude/commands/{dirName}.md для всіх локальних скілів з .cursor/skills/
|
|
757
|
-
* що не керуються пакетом (відсутні в configSkills).
|
|
772
|
+
* що не керуються пакетом (відсутні в configSkills). Frontmatter `description` — як у відповідному SKILL.md.
|
|
758
773
|
* @param {string[]} configSkills id керованих skills (вже оброблені syncCommands)
|
|
759
774
|
* @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
|
|
760
775
|
*/
|
|
@@ -778,14 +793,15 @@ async function syncLocalOnlySkillCommands(configSkills) {
|
|
|
778
793
|
|
|
779
794
|
process.stdout.write(` ⬇ ${dirName} → ${COMMANDS_DIR}/${dirName}.md ... `)
|
|
780
795
|
try {
|
|
781
|
-
let
|
|
796
|
+
let descRaw = ''
|
|
782
797
|
if (existsSync(skillMdPath)) {
|
|
783
798
|
const raw = await readFile(skillMdPath, 'utf8')
|
|
784
799
|
const parsed = extractSkillDescription(raw)
|
|
785
|
-
if (parsed)
|
|
800
|
+
if (parsed) descRaw = parsed
|
|
786
801
|
}
|
|
787
|
-
const
|
|
788
|
-
const
|
|
802
|
+
const frontmatter = formatClaudeCommandFrontmatter(descRaw)
|
|
803
|
+
const header = `# ${dirName}\n\n`
|
|
804
|
+
const body = `${frontmatter}${header}Виконай інструкції зі скілу \`${SKILLS_DIR}/${dirName}/SKILL.md\`.\n`
|
|
789
805
|
await writeFile(destFile, body, 'utf8')
|
|
790
806
|
console.log(`✅`)
|
|
791
807
|
success++
|
package/mdc/abie.mdc
CHANGED
|
@@ -40,6 +40,8 @@ spec:
|
|
|
40
40
|
|
|
41
41
|
### HTTPRoute: спільні сервіси **`auth-run-hl`**, **`file-link-hl`**
|
|
42
42
|
|
|
43
|
+
У **HTTPRoute** у шляху з **`…/k8s/base/…`** у **`spec.hostnames`** дозволені лише **`aiml.live`**, **`*.aiml.live`** та інші піддомени **aiml.live** (перевірка в **`check-abie.mjs`**).
|
|
44
|
+
|
|
43
45
|
Ці **Service** (headless **`-hl`**) живуть у **базовому** неймспейсі **`dev`**. У маніфесті **HTTPRoute** під **`k8s`** (шар без **`ua/`** та **`ru/`** — наприклад **`…/k8s/base/hr.yaml`**) для кожного **`backendRefs`** до такого сервісу явно вкажи **`namespace: dev`** і порт **8080**:
|
|
44
46
|
|
|
45
47
|
```yaml title="…/k8s/base/hr.yaml (фрагмент)"
|
package/mdc/k8s.mdc
CHANGED
package/package.json
CHANGED
package/scripts/check-abie.mjs
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* — тоді в **`ua`/`ru` kustomization** потрібен patch на **`kind: HTTPRoute`**, **непорожній `target.name`**: **`/spec/hostnames`**
|
|
30
30
|
* (домени abie.mdc), **`/spec/parentRefs/0/namespace`** (**ua** / **ru**); для **ru** — **`gwin.yandex.cloud/rules.http.upgradeTypes: websocket`**,
|
|
31
31
|
* якщо в тому ж **`kustomization.yaml`** згадується **`HASURA_GRAPHQL_JWT_SECRET`** (Hasura + JWT).
|
|
32
|
+
* **HTTPRoute (base / dev):** у маніфесті **HTTPRoute** у шляху з сегментом **`base`** (наприклад **`…/k8s/base/hr.yaml`**) у **`spec.hostnames`** дозволені лише **`aiml.live`**, **`*.aiml.live`** та інші піддомени **aiml.live** (канонічно порівняння без урахування регістру).
|
|
32
33
|
* **Спільні бекенди (`auth-run-hl`, `file-link-hl`):** у **HTTPRoute** під **`k8s`** поза overlay **ua** та **ru** (шлях не містить **`k8s/ua/`** чи **`k8s/ru/`**) кожен такий **`backendRefs`** має **`namespace: dev`** і порт **8080**;
|
|
33
34
|
* у patch overlay **ua** та **ru** — по одному **JSON6902** на **`/spec/rules/…/backendRefs/…/namespace`** з **`value`**: **ua** або **ru** (кількість patch-ів = кількість таких **`backendRefs`** у пакеті).
|
|
34
35
|
* Вибір **`op`** — **k8s.mdc**.
|
|
@@ -63,6 +64,9 @@ const ABIE_SHARED_CROSS_NS_BACKEND_SET = new Set(ABIE_SHARED_CROSS_NS_BACKEND_NA
|
|
|
63
64
|
/** Очікуваний URL **`$schema`** для **hc.yaml** (abie.mdc). */
|
|
64
65
|
export const ABIE_HC_SCHEMA_URL = 'https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json'
|
|
65
66
|
|
|
67
|
+
/** Кореневий домен **`spec.hostnames`** для **HTTPRoute** у **`…/k8s/base/…`** (середовище dev, abie.mdc). */
|
|
68
|
+
export const ABIE_BASE_DEV_HTTPROUTE_HOST_ROOT = 'aiml.live'
|
|
69
|
+
|
|
66
70
|
const MODELINE_RE = /^#\s*yaml-language-server:\s*\$schema=(\S+)\s*$/
|
|
67
71
|
const LINE_SPLIT_RE = /\r?\n/u
|
|
68
72
|
const RU_KUSTOMIZATION_PATH_RE = /(^|\/)ru\/kustomization\.yaml$/u
|
|
@@ -191,6 +195,84 @@ export function isAbieK8sBaseYamlPath(rel) {
|
|
|
191
195
|
return BASE_SEGMENT_RE.test(norm)
|
|
192
196
|
}
|
|
193
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Чи **hostname** дозволений для **HTTPRoute** у **base** (dev): **aiml.live**, **\*.aiml.live** або **\*.…\.aiml.live** (без урахування регістру).
|
|
200
|
+
* @param {string} hostname значення з **spec.hostnames**
|
|
201
|
+
* @returns {boolean} **true**, якщо hostname відповідає abie.mdc
|
|
202
|
+
*/
|
|
203
|
+
export function isAllowedAbieBaseDevHostname(hostname) {
|
|
204
|
+
if (typeof hostname !== 'string') {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
const h = hostname.trim().toLowerCase()
|
|
208
|
+
if (h === '') {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
const root = ABIE_BASE_DEV_HTTPROUTE_HOST_ROOT
|
|
212
|
+
if (h === root) {
|
|
213
|
+
return true
|
|
214
|
+
}
|
|
215
|
+
if (h === `*.${root}`) {
|
|
216
|
+
return true
|
|
217
|
+
}
|
|
218
|
+
if (h.endsWith(`.${root}`)) {
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Повідомлення про недопустимі **spec.hostnames** у **HTTPRoute** у шляху **…/base/…** (abie.mdc).
|
|
226
|
+
* @param {unknown} obj корінь YAML-документа
|
|
227
|
+
* @param {string} rel відносний шлях від кореня репозиторію
|
|
228
|
+
* @returns {string[]} порожньо, якщо перевірка не застосовується або hostnames коректні
|
|
229
|
+
*/
|
|
230
|
+
export function abieBaseHttpRouteHostnamesErrors(obj, rel) {
|
|
231
|
+
if (!isAbieK8sBaseYamlPath(rel)) {
|
|
232
|
+
return []
|
|
233
|
+
}
|
|
234
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
235
|
+
return []
|
|
236
|
+
}
|
|
237
|
+
const rec = /** @type {Record<string, unknown>} */ (obj)
|
|
238
|
+
if (rec.kind !== 'HTTPRoute') {
|
|
239
|
+
return []
|
|
240
|
+
}
|
|
241
|
+
const spec = rec.spec
|
|
242
|
+
if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) {
|
|
243
|
+
return []
|
|
244
|
+
}
|
|
245
|
+
const hostnames = /** @type {Record<string, unknown>} */ (spec).hostnames
|
|
246
|
+
if (hostnames === undefined) {
|
|
247
|
+
return []
|
|
248
|
+
}
|
|
249
|
+
/** @type {string[]} */
|
|
250
|
+
const hosts = []
|
|
251
|
+
if (Array.isArray(hostnames)) {
|
|
252
|
+
for (const h of hostnames) {
|
|
253
|
+
if (typeof h === 'string' && h.trim() !== '') {
|
|
254
|
+
hosts.push(h)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else if (typeof hostnames === 'string' && hostnames.trim() !== '') {
|
|
258
|
+
hosts.push(hostnames)
|
|
259
|
+
}
|
|
260
|
+
if (hosts.length === 0) {
|
|
261
|
+
return []
|
|
262
|
+
}
|
|
263
|
+
const root = ABIE_BASE_DEV_HTTPROUTE_HOST_ROOT
|
|
264
|
+
/** @type {string[]} */
|
|
265
|
+
const errors = []
|
|
266
|
+
for (const h of hosts) {
|
|
267
|
+
if (!isAllowedAbieBaseDevHostname(h)) {
|
|
268
|
+
errors.push(
|
|
269
|
+
`${rel}: HTTPRoute у base (dev): hostname "${h}" недопустимий — дозволені лише ${root} та піддомени, зокрема *.${root} (abie.mdc)`
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return errors
|
|
274
|
+
}
|
|
275
|
+
|
|
194
276
|
/**
|
|
195
277
|
* Чи значення **`preem`** у base **Deployment** вважається «істинним» за abie.mdc (**true** або рядок **`true`** без урахування регістру).
|
|
196
278
|
* @param {unknown} v значення з YAML
|
|
@@ -1050,7 +1132,8 @@ export async function analyzeAbieSharedBackendRefsInPackageK8s(root, pkgAbs, yam
|
|
|
1050
1132
|
if (docs) {
|
|
1051
1133
|
for (const doc of docs) {
|
|
1052
1134
|
if (doc.errors.length === 0) {
|
|
1053
|
-
const
|
|
1135
|
+
const json = doc.toJSON()
|
|
1136
|
+
const st = httpRouteDocSharedCrossNsBackendStats(json, rel)
|
|
1054
1137
|
refCount += st.refCount
|
|
1055
1138
|
baseErrors.push(...st.errors)
|
|
1056
1139
|
}
|
|
@@ -1504,6 +1587,60 @@ async function checkHttpRouteKustomization(abs, rel, mode, root, yamlFilesAbs, c
|
|
|
1504
1587
|
return true
|
|
1505
1588
|
}
|
|
1506
1589
|
|
|
1590
|
+
/**
|
|
1591
|
+
* Для кожного **HTTPRoute** у **`…/k8s/base/…`** з непорожніми **`spec.hostnames`** — лише **aiml.live** та піддомени (abie.mdc).
|
|
1592
|
+
* @param {string} root корінь репозиторію
|
|
1593
|
+
* @param {string[]} yamlFilesAbs yaml під k8s
|
|
1594
|
+
* @param {(msg: string) => void} fail callback при помилці
|
|
1595
|
+
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
1596
|
+
* @returns {Promise<void>}
|
|
1597
|
+
*/
|
|
1598
|
+
async function ensureAbieBaseHttpRouteHostnames(root, yamlFilesAbs, fail, passFn) {
|
|
1599
|
+
let baseHttpRoutesWithHostnames = 0
|
|
1600
|
+
for (const abs of yamlFilesAbs) {
|
|
1601
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
1602
|
+
if (isAbieK8sBaseYamlPath(rel)) {
|
|
1603
|
+
const docs = await readAndParseYamlDocs(abs, rel, fail)
|
|
1604
|
+
if (!docs) {
|
|
1605
|
+
return
|
|
1606
|
+
}
|
|
1607
|
+
for (const doc of docs) {
|
|
1608
|
+
if (doc.errors.length === 0) {
|
|
1609
|
+
const json = doc.toJSON()
|
|
1610
|
+
const errs = abieBaseHttpRouteHostnamesErrors(json, rel)
|
|
1611
|
+
if (errs.length > 0) {
|
|
1612
|
+
for (const e of errs) {
|
|
1613
|
+
fail(e)
|
|
1614
|
+
}
|
|
1615
|
+
return
|
|
1616
|
+
}
|
|
1617
|
+
if (json !== null && typeof json === 'object' && !Array.isArray(json)) {
|
|
1618
|
+
const rec = /** @type {Record<string, unknown>} */ (json)
|
|
1619
|
+
if (rec.kind === 'HTTPRoute') {
|
|
1620
|
+
const spec = rec.spec
|
|
1621
|
+
if (spec !== null && typeof spec === 'object' && !Array.isArray(spec)) {
|
|
1622
|
+
const hostnames = /** @type {Record<string, unknown>} */ (spec).hostnames
|
|
1623
|
+
if (Array.isArray(hostnames) && hostnames.some(h => typeof h === 'string' && h.trim() !== '')) {
|
|
1624
|
+
baseHttpRoutesWithHostnames++
|
|
1625
|
+
} else if (typeof hostnames === 'string' && hostnames.trim() !== '') {
|
|
1626
|
+
baseHttpRoutesWithHostnames++
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (baseHttpRoutesWithHostnames > 0) {
|
|
1636
|
+
passFn(
|
|
1637
|
+
`HTTPRoute у …/k8s/base/…: spec.hostnames відповідають ${ABIE_BASE_DEV_HTTPROUTE_HOST_ROOT} та піддоменам (abie.mdc)`
|
|
1638
|
+
)
|
|
1639
|
+
} else {
|
|
1640
|
+
passFn('Немає HTTPRoute у …/k8s/base/… з непорожніми spec.hostnames — перевірку aiml.live пропущено')
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1507
1644
|
/**
|
|
1508
1645
|
* Якщо є **Deployment** під **k8s**, вимагає в overlay **ua** та **ru** patch **HTTPRoute** (непорожній **target.name**) за abie.mdc
|
|
1509
1646
|
* лише для пакетів з **vite.config.{js,mjs,ts}** у каталозі пакета (батько **k8s**).
|
|
@@ -1959,6 +2096,9 @@ export async function check() {
|
|
|
1959
2096
|
pass('Перевіряємо Service → NodePort у ru/kustomization (abie.mdc)')
|
|
1960
2097
|
await ensureRuAbieServiceNodePortPatches(root, yamlFiles, fail, pass)
|
|
1961
2098
|
|
|
2099
|
+
pass('Перевіряємо HTTPRoute spec.hostnames у …/k8s/base/… (aiml.live, abie.mdc)')
|
|
2100
|
+
await ensureAbieBaseHttpRouteHostnames(root, yamlFiles, fail, pass)
|
|
2101
|
+
|
|
1962
2102
|
if (deploymentDirs.size > 0) {
|
|
1963
2103
|
pass('Є Deployment — перевіряємо nodeSelector у ua/ru kustomization (abie.mdc)')
|
|
1964
2104
|
await ensureUaRuAbieNodeSelectorPatches(root, yamlFiles, deploymentDirs, fail, pass)
|