@nitra/cursor 12.8.4 → 12.8.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.8.5] - 2026-06-22
4
+
5
+ ### Changed
6
+
7
+ - ♻️ refactor(npm): Перехід з `style-lint` на `style` у правил та конфігах
8
+
3
9
  ## [12.8.4] - 2026-06-22
4
10
 
5
11
  ### Changed
package/bin/n-cursor.js CHANGED
@@ -74,7 +74,7 @@ import { fileURLToPath } from 'node:url'
74
74
 
75
75
  import { buildAgentsCommandBulletItems } from '../scripts/build-agents-commands.mjs'
76
76
  import { formatGeneratedMarkdownLines, renderAgentsTemplate } from '../scripts/lib/generated-markdown.mjs'
77
- import { inlineTemplateLinks } from '../scripts/lib/inline-template-links.mjs'
77
+ import { inlineMarkdownIncludes, inlineTemplateLinks } from '../scripts/lib/inline-template-links.mjs'
78
78
  import {
79
79
  detectAutoRules,
80
80
  detectLegacyRuleIds,
@@ -422,7 +422,8 @@ async function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR)
422
422
  )
423
423
  }
424
424
  const text = await readFile(bundledPath, 'utf8')
425
- return inlineTemplateLinks(text, dirname(bundledPath))
425
+ const withTemplates = await inlineTemplateLinks(text, dirname(bundledPath))
426
+ return inlineMarkdownIncludes(withTemplates, dirname(bundledPath))
426
427
  }
427
428
 
428
429
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.8.4",
3
+ "version": "12.8.5",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,33 @@
1
+ ## Внутрішньокластерні URL у env-файлах (dev / ua)
2
+
3
+ Правило стосується **будь-якого** внутрішньокластерного URL у env-файлах abie-проєкту, а не лише `HASURA_GRAPHQL_ENDPOINT`. Це може бути URL до Hasura, KVCMS, `auth-run-hl`, `file-link-hl` чи будь-якого іншого Service у кластері — у всіх випадках DNS-суфікс і namespace-префікс мають відповідати **середовищу** з імені env-файлу.
4
+
5
+ abie-проєкти живуть у **двох GKE-кластерах** (dev / ua), тож DNS-суфікс і namespace у URL відрізняються між `*.env`-файлами:
6
+
7
+ | env-файл (basename) | namespace-префікс у URL | DNS-суфікс кластера | примітка |
8
+ | --- | --- | --- | --- |
9
+ | `dev.env`, `.dev.env` | `dev-…` | `abie-dev.internal` | GKE-кластер dev |
10
+ | `ua.env`, `.ua.env` | `ua-…` | `abie-ua.internal` | GKE-кластер ua |
11
+
12
+ Канонічна форма URL — `http://<service>.<namespace>.svc.<cluster-dns-suffix>:<port>`. Суфікс — `<cluster>.internal`.
13
+
14
+ Приклади для одного env-файлу з двома сервісами (Hasura + KVCMS):
15
+
16
+ ```env title="hasura/.dev.env"
17
+ HASURA_GRAPHQL_ENDPOINT=http://apruv-h-hl.dev-apruv.svc.abie-dev.internal:8080
18
+ KVCMS_URL=http://kvcms-hl.dev-apruv.svc.abie-dev.internal:8080
19
+ ```
20
+
21
+ ```env title="hasura/.ua.env"
22
+ HASURA_GRAPHQL_ENDPOINT=http://apruv-h-hl.ua-apruv.svc.abie-ua.internal:8080
23
+ KVCMS_URL=http://kvcms-hl.ua-apruv.svc.abie-ua.internal:8080
24
+ ```
25
+
26
+ `<namespace>` (наприклад `dev-apruv` / `ua-apruv`) — `metadata.name` цільового namespace після kustomize-overlay для відповідного середовища; `<service>` — `metadata.name` headless Service (`-hl`) того сервісу, до якого йде URL.
27
+
28
+ **Перевірка `js/env_dns.mjs`** сканує всі `*.env` файли, basename яких збігається з `dev.env` / `ua.env` (з провідною крапкою чи без), знаходить **усі** internal URL (`http://<svc>.<ns>.svc.<dns>` — як для Hasura-ендпоінта, так і для KVCMS чи будь-якого іншого) і вимагає, щоб для кожного:
29
+
30
+ - DNS-суфікс відповідав env: `abie-dev.internal` / `abie-ua.internal`;
31
+ - namespace починався з `dev-` / `ua-` відповідно.
32
+
33
+ Загальне правило про **внутрішній** URL (не публічний домен) для `HASURA_GRAPHQL_ENDPOINT` лишається у **`hasura.mdc`** (для nitra і abie) — `rules/hasura/fix.mjs` приймає кластерний DNS-формат `<cluster>.internal`.
@@ -0,0 +1,3 @@
1
+ ## Firebase Hosting
2
+
3
+ У **кожному** підкаталозі, що лежить **безпосередньо** в корені репозиторію, не тримати конфіг і кеш **Firebase Hosting**: у таких каталогах не повинно бути **`.firebaserc`**, **`firebase.json`** та каталогу **`.firebase/`** (у **самому** корені репозиторію ці імена перевіркою abie **не** розглядаються; `node_modules` / `.git` зі скану вилучаються).
@@ -0,0 +1,23 @@
1
+ ## k8s: `hc.yaml` поруч із Deployment
2
+
3
+ Якщо під **`k8s`** є **Deployment**, у **тій самій директорії** має бути **`hc.yaml`** з **HealthCheckPolicy** (**`networking.gke.io/v1`**): коректний modeline **`$schema`**, **`httpHealthCheck.requestPath`** — непорожній шлях від кореня (рядок, що починається з **`/`**: канонічно **`/healthz`**, але також допустимі **`/IsAlive`**, **`/api/live`** тощо — узгоджується з реальним endpoint сервісу), порт **8080**, **`targetRef.name`** — **headless** **Service** з суфіксом **`-hl`** (узгоджено з парою **`svc.yaml`** / **`svc-hl.yaml`** у **k8s.mdc**): або **`${metadata.name}-hl`**, або те саме ім'я, якщо **`metadata.name`** уже з **`-hl`**.
4
+
5
+ ```yaml title="hc.yaml"
6
+ # yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
7
+ apiVersion: networking.gke.io/v1
8
+ kind: HealthCheckPolicy
9
+ metadata:
10
+ name: СЕРВІС
11
+ namespace: dev # kustomize overlay
12
+ spec:
13
+ default:
14
+ config:
15
+ type: HTTP
16
+ httpHealthCheck:
17
+ requestPath: /healthz
18
+ port: 8080
19
+ targetRef:
20
+ group: ''
21
+ kind: Service
22
+ name: СЕРВІС-hl
23
+ ```
@@ -0,0 +1,47 @@
1
+ ## k8s: overlay **HTTPRoute** (**ua**)
2
+
3
+ За наявності **Deployment** під **k8s** і наявності **Vite** (**`vite.config.js`**, **`vite.config.mjs`** або **`vite.config.ts`** у каталозі пакета) у **`ua/kustomization.yaml`** цього пакета потрібні **inline JSON6902** у **`patches`**: **target** **`kind: HTTPRoute`**, **непорожній `name`** (як у маніфесті маршруту). Мають бути зміни **`/spec/hostnames`** (домени abie — у скрипті) та **`/spec/parentRefs/0/namespace`** (**`ua`**, також дозволені префікси **`ua-*`**, наприклад **`ua-b2b`**). Як обирати **`op`** (**add** / **replace** тощо) у patch — **k8s.mdc** (розділ про JSON patch у kustomization).
4
+
5
+ ### HTTPRoute: спільні сервіси **`auth-run-hl`**, **`file-link-hl`**
6
+
7
+ У **HTTPRoute** у шляху з **`…/k8s/base/…`** у **`spec.hostnames`** дозволені лише **`aiml.live`**, **`*.aiml.live`** та інші піддомени **aiml.live** (перевірка — Rego-пакет **`abie.http_route_base`**, див. розділ нижче).
8
+
9
+ Ці **Service** (headless **`-hl`**) живуть у **базовому** неймспейсі **`dev`**. У маніфесті **HTTPRoute** під **`k8s`** (шар без **`ua/`** — наприклад **`…/k8s/base/hr.yaml`**) для кожного **`backendRefs`** до такого сервісу явно вкажи **`namespace: dev`** і порт **8080**:
10
+
11
+ ```yaml title="…/k8s/base/hr.yaml (фрагмент)"
12
+ spec:
13
+ rules:
14
+ - matches:
15
+ - path:
16
+ type: PathPrefix
17
+ value: /
18
+ backendRefs:
19
+ - name: auth-run-hl
20
+ namespace: dev
21
+ port: 8080
22
+ - name: file-link-hl
23
+ namespace: dev
24
+ port: 8080
25
+ ```
26
+
27
+ У **`ua/kustomization.yaml`** додай до того самого **inline** patch на **`HTTPRoute`** (той самий **`target.name`**) операції **JSON6902** з **`path`**: **`/spec/rules/<i>/backendRefs/<j>/namespace`**, де **`<i>`** / **`<j>`** — індекси відповідно до порядку **`spec.rules`** та **`backendRefs`** у base-файлі; **`value`**: **`ua`** (також дозволені **`ua-*`**). Якщо кілька таких **`backendRefs`**, потрібна окрема операція для кожного.
28
+
29
+ ```yaml title="…/ua/kustomization.yaml (фрагмент)"
30
+ - target:
31
+ kind: HTTPRoute
32
+ name: my-httproute
33
+ patch: |-
34
+ - op: replace
35
+ path: /spec/hostnames
36
+ value:
37
+ - "abie.app" # зокрема vybeerai.com.ua, *.vybeerai.com.ua, *.abie.app
38
+ - op: replace
39
+ path: /spec/parentRefs/0/namespace
40
+ value: ua
41
+ - op: replace
42
+ path: /spec/rules/0/backendRefs/0/namespace
43
+ value: ua
44
+ - op: replace
45
+ path: /spec/rules/0/backendRefs/1/namespace
46
+ value: ua
47
+ ```
@@ -0,0 +1,27 @@
1
+ ## k8s: overlay **ua** і nodeSelector
2
+
3
+ У **`…/ua/kustomization.yaml`** того пакета, у дереві **`k8s`** якого є **Deployment**, потрібен patch на **`kind: Deployment`**: **`spec.template.spec.nodeSelector`** з **`preem: false`**. Форму **JSON6902** (шлях **`/spec/template/spec/nodeSelector`**, **`op`**) див. **k8s.mdc**.
4
+
5
+ ```yaml title="…/ua/kustomization.yaml (фрагмент)"
6
+ patches:
7
+ - target:
8
+ kind: Deployment
9
+ name: my-app
10
+ patch: |-
11
+ - op: add
12
+ path: /spec/template/spec/nodeSelector
13
+ value:
14
+ preem: 'false'
15
+ ```
16
+
17
+ ### Базовий Deployment (`…/base/`)
18
+
19
+ Якщо **Deployment** у YAML під **`k8s`** лежить у шляху з сегментом **`base`**, у **`spec.template.spec.nodeSelector`** має бути **`preem`** зі значенням **істинно** (**`true`** або рядок **`'true'`**); overlay **ua** підміняє селектор.
20
+
21
+ ```yaml title="…/base/deploy.yaml (фрагмент)"
22
+ spec:
23
+ template:
24
+ spec:
25
+ nodeSelector:
26
+ preem: 'true' # буде замінено через kustomize
27
+ ```
@@ -6,139 +6,13 @@ version: '1.22'
6
6
 
7
7
  Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**, для спільних сервісів **`auth-run-hl`** / **`file-link-hl`** — **`namespace: dev`** у base та patch **`…/backendRefs/…/namespace`** у **ua**)), гілки **dev**, **ua** у **clean-merged-branch**, а також заборона тримати артефакти **Firebase Hosting** у **підкаталогах першого рівня** (безпосередні діти кореня репозиторію; у самому корені ці імена не вимагаються до видалення).
8
8
 
9
- ## k8s: `hc.yaml` поруч із Deployment
10
-
11
- Якщо під **`k8s`** є **Deployment**, у **тій самій директорії** має бути **`hc.yaml`** з **HealthCheckPolicy** (**`networking.gke.io/v1`**): коректний modeline **`$schema`**, **`httpHealthCheck.requestPath`** — непорожній шлях від кореня (рядок, що починається з **`/`**: канонічно **`/healthz`**, але також допустимі **`/IsAlive`**, **`/api/live`** тощо — узгоджується з реальним endpoint сервісу), порт **8080**, **`targetRef.name`** — **headless** **Service** з суфіксом **`-hl`** (узгоджено з парою **`svc.yaml`** / **`svc-hl.yaml`** у **k8s.mdc**): або **`${metadata.name}-hl`**, або те саме ім’я, якщо **`metadata.name`** уже з **`-hl`**.
12
-
13
- ```yaml title="hc.yaml"
14
- # yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
15
- apiVersion: networking.gke.io/v1
16
- kind: HealthCheckPolicy
17
- metadata:
18
- name: СЕРВІС
19
- namespace: dev # kustomize overlay
20
- spec:
21
- default:
22
- config:
23
- type: HTTP
24
- httpHealthCheck:
25
- requestPath: /healthz
26
- port: 8080
27
- targetRef:
28
- group: ''
29
- kind: Service
30
- name: СЕРВІС-hl
31
- ```
32
-
33
- ## k8s: overlay **HTTPRoute** (**ua**)
34
-
35
- За наявності **Deployment** під **k8s** і наявності **Vite** (**`vite.config.js`**, **`vite.config.mjs`** або **`vite.config.ts`** у каталозі пакета) у **`ua/kustomization.yaml`** цього пакета потрібні **inline JSON6902** у **`patches`**: **target** **`kind: HTTPRoute`**, **непорожній `name`** (як у маніфесті маршруту). Мають бути зміни **`/spec/hostnames`** (домени abie — у скрипті) та **`/spec/parentRefs/0/namespace`** (**`ua`**, також дозволені префікси **`ua-*`**, наприклад **`ua-b2b`**). Як обирати **`op`** (**add** / **replace** тощо) у patch — **k8s.mdc** (розділ про JSON patch у kustomization).
36
-
37
- ### HTTPRoute: спільні сервіси **`auth-run-hl`**, **`file-link-hl`**
38
-
39
- У **HTTPRoute** у шляху з **`…/k8s/base/…`** у **`spec.hostnames`** дозволені лише **`aiml.live`**, **`*.aiml.live`** та інші піддомени **aiml.live** (перевірка — Rego-пакет **`abie.http_route_base`**, див. розділ нижче).
40
-
41
- Ці **Service** (headless **`-hl`**) живуть у **базовому** неймспейсі **`dev`**. У маніфесті **HTTPRoute** під **`k8s`** (шар без **`ua/`** — наприклад **`…/k8s/base/hr.yaml`**) для кожного **`backendRefs`** до такого сервісу явно вкажи **`namespace: dev`** і порт **8080**:
42
-
43
- ```yaml title="…/k8s/base/hr.yaml (фрагмент)"
44
- spec:
45
- rules:
46
- - matches:
47
- - path:
48
- type: PathPrefix
49
- value: /
50
- backendRefs:
51
- - name: auth-run-hl
52
- namespace: dev
53
- port: 8080
54
- - name: file-link-hl
55
- namespace: dev
56
- port: 8080
57
- ```
58
-
59
- У **`ua/kustomization.yaml`** додай до того самого **inline** patch на **`HTTPRoute`** (той самий **`target.name`**) операції **JSON6902** з **`path`**: **`/spec/rules/<i>/backendRefs/<j>/namespace`**, де **`<i>`** / **`<j>`** — індекси відповідно до порядку **`spec.rules`** та **`backendRefs`** у base-файлі; **`value`**: **`ua`** (також дозволені **`ua-*`**). Якщо кілька таких **`backendRefs`**, потрібна окрема операція для кожного.
60
-
61
- ```yaml title="…/ua/kustomization.yaml (фрагмент)"
62
- - target:
63
- kind: HTTPRoute
64
- name: my-httproute
65
- patch: |-
66
- - op: replace
67
- path: /spec/hostnames
68
- value:
69
- - "abie.app" # зокрема vybeerai.com.ua, *.vybeerai.com.ua, *.abie.app
70
- - op: replace
71
- path: /spec/parentRefs/0/namespace
72
- value: ua
73
- - op: replace
74
- path: /spec/rules/0/backendRefs/0/namespace
75
- value: ua
76
- - op: replace
77
- path: /spec/rules/0/backendRefs/1/namespace
78
- value: ua
79
- ```
80
-
81
- ## k8s: overlay **ua** і nodeSelector
82
-
83
- У **`…/ua/kustomization.yaml`** того пакета, у дереві **`k8s`** якого є **Deployment**, потрібен patch на **`kind: Deployment`**: **`spec.template.spec.nodeSelector`** з **`preem: false`**. Форму **JSON6902** (шлях **`/spec/template/spec/nodeSelector`**, **`op`**) див. **k8s.mdc**.
84
-
85
- ```yaml title="…/ua/kustomization.yaml (фрагмент)"
86
- patches:
87
- - target:
88
- kind: Deployment
89
- name: my-app
90
- patch: |-
91
- - op: add
92
- path: /spec/template/spec/nodeSelector
93
- value:
94
- preem: 'false'
95
- ```
96
-
97
- ### Базовий Deployment (`…/base/`)
9
+ [k8s-hc-yaml](./js/hc_pairing.mdc)
98
10
 
99
- Якщо **Deployment** у YAML під **`k8s`** лежить у шляху з сегментом **`base`**, у **`spec.template.spec.nodeSelector`** має бути **`preem`** зі значенням **істинно** (**`true`** або рядок **`'true'`**); overlay **ua** підміняє селектор.
11
+ [k8s-http-route-ua](./js/ua_http_route.mdc)
100
12
 
101
- ```yaml title="…/base/deploy.yaml (фрагмент)"
102
- spec:
103
- template:
104
- spec:
105
- nodeSelector:
106
- preem: 'true' # буде замінено через kustomize
107
- ```
108
-
109
- ## Внутрішньокластерні URL у env-файлах (dev / ua)
110
-
111
- Правило стосується **будь-якого** внутрішньокластерного URL у env-файлах abie-проєкту, а не лише `HASURA_GRAPHQL_ENDPOINT`. Це може бути URL до Hasura, KVCMS, `auth-run-hl`, `file-link-hl` чи будь-якого іншого Service у кластері — у всіх випадках DNS-суфікс і namespace-префікс мають відповідати **середовищу** з імені env-файлу.
112
-
113
- abie-проєкти живуть у **двох GKE-кластерах** (dev / ua), тож DNS-суфікс і namespace у URL відрізняються між `*.env`-файлами:
13
+ [k8s-nodeselector](./js/ua_node_selector.mdc)
114
14
 
115
- | env-файл (basename) | namespace-префікс у URL | DNS-суфікс кластера | примітка |
116
- | --- | --- | --- | --- |
117
- | `dev.env`, `.dev.env` | `dev-…` | `abie-dev.internal` | GKE-кластер dev |
118
- | `ua.env`, `.ua.env` | `ua-…` | `abie-ua.internal` | GKE-кластер ua |
119
-
120
- Канонічна форма URL — `http://<service>.<namespace>.svc.<cluster-dns-suffix>:<port>`. Суфікс — `<cluster>.internal`.
121
-
122
- Приклади для одного env-файлу з двома сервісами (Hasura + KVCMS):
123
-
124
- ```env title="hasura/.dev.env"
125
- HASURA_GRAPHQL_ENDPOINT=http://apruv-h-hl.dev-apruv.svc.abie-dev.internal:8080
126
- KVCMS_URL=http://kvcms-hl.dev-apruv.svc.abie-dev.internal:8080
127
- ```
128
-
129
- ```env title="hasura/.ua.env"
130
- HASURA_GRAPHQL_ENDPOINT=http://apruv-h-hl.ua-apruv.svc.abie-ua.internal:8080
131
- KVCMS_URL=http://kvcms-hl.ua-apruv.svc.abie-ua.internal:8080
132
- ```
133
-
134
- `<namespace>` (наприклад `dev-apruv` / `ua-apruv`) — `metadata.name` цільового namespace після kustomize-overlay для відповідного середовища; `<service>` — `metadata.name` headless Service (`-hl`) того сервісу, до якого йде URL.
135
-
136
- **Перевірка `js/env_dns.mjs`** сканує всі `*.env` файли, basename яких збігається з `dev.env` / `ua.env` (з провідною крапкою чи без), знаходить **усі** internal URL (`http://<svc>.<ns>.svc.<dns>` — як для Hasura-ендпоінта, так і для KVCMS чи будь-якого іншого) і вимагає, щоб для кожного:
137
-
138
- - DNS-суфікс відповідав env: `abie-dev.internal` / `abie-ua.internal`;
139
- - namespace починався з `dev-` / `ua-` відповідно.
140
-
141
- Загальне правило про **внутрішній** URL (не публічний домен) для `HASURA_GRAPHQL_ENDPOINT` лишається у **`hasura.mdc`** (для nitra і abie) — `rules/hasura/fix.mjs` приймає кластерний DNS-формат `<cluster>.internal`.
15
+ [env-dns](./js/env_dns.mdc)
142
16
 
143
17
  ## `@nitra/abie-shared` у `devDependencies`
144
18
 
@@ -148,9 +22,7 @@ KVCMS_URL=http://kvcms-hl.ua-apruv.svc.abie-ua.internal:8080
148
22
  bun add -d @nitra/abie-shared
149
23
  ```
150
24
 
151
- ## Firebase Hosting
152
-
153
- У **кожному** підкаталозі, що лежить **безпосередньо** в корені репозиторію, не тримати конфіг і кеш **Firebase Hosting**: у таких каталогах не повинно бути **`.firebaserc`**, **`firebase.json`** та каталогу **`.firebase/`** (у **самому** корені репозиторію ці імена перевіркою abie **не** розглядаються; `node_modules` / `.git` зі скану вилучаються).
25
+ [firebase](./js/firebase_hosting.mdc)
154
26
 
155
27
  ## Git branches
156
28
 
@@ -6,19 +6,19 @@ resource: npm/rules/doc-files/js/
6
6
 
7
7
  # npm/rules/doc-files/js
8
8
 
9
- | Файл | Тип |
10
- |---|---|
11
- | [docgen-crc.mjs](docgen-crc.md) | JS Module |
9
+ | Файл | Тип |
10
+ | ------------------------------------------------------- | --------- |
11
+ | [docgen-crc.mjs](docgen-crc.md) | JS Module |
12
12
  | [docgen-extract-anchors.mjs](docgen-extract-anchors.md) | JS Module |
13
- | [docgen-extract.mjs](docgen-extract.md) | JS Module |
14
- | [docgen-files-batch.mjs](docgen-files-batch.md) | JS Module |
15
- | [docgen-gen.mjs](docgen-gen.md) | JS Module |
16
- | [docgen-ignore.mjs](docgen-ignore.md) | JS Module |
17
- | [docgen-judge-measure.mjs](docgen-judge-measure.md) | JS Module |
18
- | [docgen-judge.mjs](docgen-judge.md) | JS Module |
19
- | [docgen-prompts.mjs](docgen-prompts.md) | JS Module |
20
- | [docgen-scan.mjs](docgen-scan.md) | JS Module |
21
- | [run-lint.mjs](run-lint.md) | JS Module |
22
- | [units-js.mjs](units-js.md) | JS Module |
23
- | [units-rs.mjs](units-rs.md) | JS Module |
24
- | [units.mjs](units.md) | JS Module |
13
+ | [docgen-extract.mjs](docgen-extract.md) | JS Module |
14
+ | [docgen-files-batch.mjs](docgen-files-batch.md) | JS Module |
15
+ | [docgen-gen.mjs](docgen-gen.md) | JS Module |
16
+ | [docgen-ignore.mjs](docgen-ignore.md) | JS Module |
17
+ | [docgen-judge-measure.mjs](docgen-judge-measure.md) | JS Module |
18
+ | [docgen-judge.mjs](docgen-judge.md) | JS Module |
19
+ | [docgen-prompts.mjs](docgen-prompts.md) | JS Module |
20
+ | [docgen-scan.mjs](docgen-scan.md) | JS Module |
21
+ | [run-lint.mjs](run-lint.md) | JS Module |
22
+ | [units-js.mjs](units-js.md) | JS Module |
23
+ | [units-rs.mjs](units-rs.md) | JS Module |
24
+ | [units.mjs](units.md) | JS Module |
@@ -10,19 +10,19 @@ docgen:
10
10
 
11
11
  ## Огляд
12
12
 
13
- Дозволяє запускати повний аналіз проєкту за допомогою `run`, виконувати лінтинг за допомогою `lint` або фільтрувати файли з розширенням JavaScript за допомогою `filterJsFiles`.
13
+ Модуль забезпечує виконання функцій `run`, `filterJsFiles` та `lint` для аналізу кодової бази. Він виконує стандартну перевірку проєкту, відбираючи файли з розширеннями JavaScript за допомогою `filterJsFiles` та запускаючи перевірку JS-коду за допомогою `lint`.
14
14
 
15
15
  ## Поведінка
16
16
 
17
17
  run виконує стандартну перевірку проєкту.
18
- filterJsFiles відбирає з наданого списку лише файли, що мають розширення, характерне для JavaScript.
19
- lint запускає перевірку проєкту: або повний аналіз усіх файлів, або класифікований аналіз лише змінених файлів.
18
+ filterJsFiles відбирає з наданого списку лише файли з розширеннями, характерними для JavaScript.
19
+ lint запускає перевірку JS-коду, виконуючи або повний аналіз проєкту, або класифіковану перевірку змінених файлів.
20
20
 
21
21
  ## Публічний API
22
22
 
23
- run — виконує повний цикл перевірки: аналіз конфігурації, перевірку логіки та посилання на метадані.
24
- filterJsFiles — відбирає лише файли з розширенням JavaScript.
25
- lint — запускає інструменти для форматування та стилізації коду (oxlint, eslint, jscpd, knip).
23
+ run — основна точка входу для виконання правил, що включає перевірку логіки застосування, логіки політик та посилань на метадані.
24
+ filterJsFiles — відбирає лише файли, схожі на JavaScript, з наданого списку.
25
+ lint — запускає інструменти oxlint та eslint (для окремих файлів або всього проекту) та jscpd+knip (тільки для всього проекту), з можливістю автоматичного виправлення або лише виявлення проблем.
26
26
 
27
27
  ## Гарантії поведінки
28
28
 
@@ -5,23 +5,24 @@ resource: npm/rules/js/js/check.mjs
5
5
  docgen:
6
6
  crc: 7ad4aa59
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 90
8
+ score: 100
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль перевіряє відповідність структури та конфігураційних файлів проєкту встановленим стандартам. Він валідує файли, такі як `package.json`, `.oxlintrc.json`, `.eslintrc.json` та файли робочих процесів GitHub, відповідно до вимог (js.mdc). Перевірка свідомо ігнорує шляхи `.github` та `.git`. Модуль забезпечує перехоплення помилок (fail-safe) під час виконання перевірок.
13
+ Модуль виконує валідацію конфігураційних файлів, включаючи `package.json`, `.oxlintrc.json`, `knip.json`, `knip-canonical.json` та `.eslintrc.json`. Він перевіряє відповідність структури та конфігурацій встановленим стандартам. (js.mdc) (text.mdc)
14
14
 
15
15
  ## Поведінка
16
16
 
17
- 1. Викликати `check` для запуску перевірок конфігурації проєкту.
18
- 2. Перевірити наявність конфігурацій ESLint (flat config) та валідувати їх вміст відповідно до вимог (js.mdc).
19
- 3. Перевірити `package.json` у корені та у всіх пакетах workspace на відповідність вимогам: наявність `"type": "module"` та мінімальні версії `engines.node` (>=24) та `engines.bun` (>=1.3) (js.mdc).
20
- 4. Перевірити конфігураційний файл `.oxlintrc.json` на відповідність канонічному шаблону (js.mdc).
21
- 5. Перевірити файли робочих процесів (`.github/workflows/lint-js.yml` та `.github/workflows/lint.yml`) на відповідність політикам лінтингу (js.mdc).
22
- 6. Перевірити наявність конфігурацій Knip (`knip.json`). Якщо відсутній, створити його, скопіювавши канонічний шаблон (js.mdc).
23
- 7. Перевірити наявність застарілих конфігурацій ESLint (наприклад, `.eslintrc.*`) та повідомити про їхнє використання.
24
- 8. Ігнорувати перевірки у каталогах `.github` та `.git`.
17
+ 1. Викликається функція check.
18
+ 2. Ініціалізується механізм збору результатів перевірок.
19
+ 3. Перевіряється конфігураційний файл ESLint.
20
+ 4. Перевіряється структура пакетів у workspace'ах, включаючи наявність `"type": "module"` та вимоги до версій Node та Bun у `package.json` кожного пакета.
21
+ 5. Перевіряється конфігураційний файл `.oxlintrc.json` на відповідність канонічному шаблону.
22
+ 6. Перевіряється структура файлів у робочих процесах GitHub, зокрема наявність `lint-js.yml` та відсутність дублювання кроків лінтингу в `lint.yml`.
23
+ 7. Перевіряється наявність `knip.json` у корені проєкту. Якщо відсутній, він створюється з канонічного шаблону.
24
+ 8. Після виконання всіх перевірок, сканується проєкт на наявність застарілих конфігураційних файлів ESLint.
25
+ 9. Повертається код виходу, що відображає загальний статус виконання перевірок.
25
26
 
26
27
  ## Публічний API
27
28
 
@@ -10,19 +10,19 @@ docgen:
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль визначає шляхи до канонічних JSON-файлів для інструментів oxlint та knip через функції OXLINT_CANONICAL_JSON_PATH та KNIP_CANONICAL_JSON_PATH. Він також надає функцію verifyOxlintRcAgainstCanonical для валідації конфігурацій, перевіряючи, чи відповідає `.oxlintrc.json` правилам, визначеним у oxlint-canonical.json.
13
+ Визначає шляхи до канонічних JSON-файлів для інструментів oxlint та knip через OXLINT_CANONICAL_JSON_PATH та KNIP_CANONICAL_JSON_PATH. Також перевіряє відповідність конфігураційного файлу .oxlintrc.json значенням, встановленим у oxlint-canonical.json, за допомогою verifyOxlintRcAgainstCanonical.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- OXLINT_CANONICAL_JSON_PATH — Вказує на шлях до канонічного JSON-файлу для oxlint у цьому пакеті.
18
- KNIP_CANONICAL_JSON_PATH — Вказує на шлях до канонічного JSON-файлу для knip у цьому пакеті.
19
- verifyOxlintRcAgainstCanonical — Перевіряє конфігурацію `.oxlintrc.json` на відповідність канонічному файлу oxlint-canonical.json, виявляючи відхилення у правилах та інших полях.
17
+ OXLINT_CANONICAL_JSON_PATH — Вказує шлях до канонічного JSON-файлу для oxlint у цьому пакеті.
18
+ KNIP_CANONICAL_JSON_PATH — Вказує шлях до канонічного JSON-файлу для knip у цьому пакеті.
19
+ verifyOxlintRcAgainstCanonical — Перевіряє конфігураційний файл `.oxlintrc.json` на відповідність канонічним значенням, визначеним у `oxlint-canonical.json`.
20
20
 
21
21
  ## Публічний API
22
22
 
23
- OXLINT_CANONICAL_JSON_PATH — вказує на файл з еталонними налаштуваннями oxlint у пакеті.
24
- KNIP_CANONICAL_JSON_PATH — вказує на файл з еталонними налаштуваннями knip, який копіюється у корінь проєкту, якщо його там немає.
25
- verifyOxlintRcAgainstCanonical — порівнює конфігураційний файл `.oxlintrc.json` з еталоном, перевіряючи, чи всі правила з еталону присутні, а інші поля збігаються з `oxlint-canonical.json`.
23
+ OXLINT_CANONICAL_JSON_PATH — Вказує на файл з еталонними налаштуваннями oxlint для валідації.
24
+ KNIP_CANONICAL_JSON_PATH — Шлях до еталонних налаштувань knip, які копіюються у корінь проєкту, якщо їх немає.
25
+ verifyOxlintRcAgainstCanonical — Порівнює конфігураційний файл `.oxlintrc.json` з еталоном, перевіряючи, чи всі правила з еталону присутні, а інші поля збігаються з каноном.
26
26
 
27
27
  ## Гарантії поведінки
28
28
 
@@ -10,21 +10,22 @@ docgen:
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль сканує файли у монорепозиторії. Він зчитує вміст JavaScript/TypeScript файлів та витягує всі рядкові імпорти. На основі конфігурації `.n-cursor.json` він перевіряє, чи не містять імпорти заборонених відносних шляхів, реєструючи відповідний статус у логіці (js.mdc). Публічна функція `check` виконує цю перевірку, свідомо пропускаючи шляхи `.git` та `node_modules`.
13
+ Модуль сканує монорепозиторій для пошуку каталогів `utils` та аналізу їхнього вмісту. Аналіз файлів JS/TS у цих каталогах здійснюється на відповідність шаблону забороненого відносного імпорту, що базується на конфігурації, визначеній у `.n-cursor.json`. При виявленні порушень, система генерує повідомлення, позначене як (js.mdc).
14
14
 
15
15
  ## Поведінка
16
16
 
17
- 1. Визначає корінь проекту.
18
- 2. Зчитує список шляхів, які слід ігнорувати, на основі конфігурації .n-cursor.json.
19
- 3. Визначає кореневі каталоги пакетів у монорепозиторії.
20
- 4. Для кожного кореневого каталогу пакета рекурсивно шукає каталоги з назвою `utils`, ігноруючи типові артефакти (наприклад, node_modules, .git, dist).
21
- 5. Якщо знайдено каталоги `utils`, для кожного з них рекурсивно збирає всі файли з розширеннями `.js`, `.jsx`, `.ts`, `.tsx`, які не є тестовими.
22
- 6. Для кожного зібраного файлу зчитує його вміст.
23
- 7. Витягує з вмісту файлу всі рядкові імпорти (статичні, динамічні та виклики `require`).
24
- 8. Для кожного витягнутого імпорту перевіряє, чи відповідає він шаблону забороненого відносного шляху, що вказує на імпорт з іншого каталогу, який не є загальним.
25
- 9. Якщо заборонений імпорт знайдено, реєструє помилку, посилаючись на файл та заборонений імпорт (js.mdc).
26
- 10. Якщо жодних порушень не знайдено, реєструє успіх (js.mdc).
27
- 11. Повертає код виходу, що відображає статус перевірки.
17
+ 1. Визначає кореневий каталог для аналізу.
18
+ 2. Зчитує шляхи, які слід ігнорувати, на основі конфігурації .n-cursor.json.
19
+ 3. Знаходить усі каталоги з назвою `utils` у межах кореневих каталогів пакетів монорепозиторію, ігноруючи типові артефакти (наприклад, node_modules, .git, dist).
20
+ 4. Якщо знайдено жодних каталогів `utils`, перевірка вважається успішною і завершується.
21
+ 5. Для кожного знайденого каталогу `utils` збирає всі джерела файлів, які відповідають шаблону JS/TS, виключаючи тестові файли та файли у каталогах `tests/` чи `__fixtures__/`.
22
+ 6. Для кожного зібраного файлу:
23
+ а. Зчитує вміст файлу.
24
+ б. Витягує всі рядкові імпорти (статичні, динамічні, `require`) з коду.
25
+ в. Перевіряє кожен витягнутий імпорт на відповідність шаблону забороненого відносного імпорту.
26
+ г. Якщо імпорт є забороненим, фіксує порушення, вказуючи повний шлях до файлу та сам імпорт.
27
+ 7. Після перевірки всіх файлів, якщо порушень не знайдено, перевірка вважається успішною (js.mdc).
28
+ 8. Повертає код завершення, що відображає успіх або виявлені порушення (js.mdc).
28
29
 
29
30
  ## Гарантії поведінки
30
31
 
@@ -10,22 +10,22 @@ docgen:
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль ініціалізує та налаштовує середовище для покриття коду, перевіряючи конфігурації, зокрема `mutation.json` та `package.json`. Він створює або оновлює конфігураційні файли Stryker, Vue-макросів та Vitest для кожного знайденого кореневого каталогу JavaScript. Функція `check` виконує перевірку, ігноруючи каталоги `node_modules`. Поведінка модуля фіксується за маркером (test.mdc). Модуль перехоплює помилки (fail-safe) і не кидає винятків назовні.
13
+ Модуль перевіряє готовність JavaScript-проєктів до виконання тестів. Він збирає кореневі каталоги проєктів, використовуючи публічну функцію `check` для валідації. Перевірка гарантує наявність необхідних конфігураційних файлів, зокрема `mutation.json` та `package.json`, у кожному знайденому проєкті. Модуль свідомо пропускає каталоги `node_modules`. Він також додає відповідні артефакти тестів до `.gitignore` у коренях проєктів. (test.mdc)
14
14
 
15
15
  ## Поведінка
16
16
 
17
17
  1. Викликається `check` для ініціалізації процесу перевірки.
18
- 2. Перевіряється, чи у конфігурації дозволено перевірку JavaScript. Якщо ні, процес завершується успішно.
19
- 3. Знаходяться всі кореневі каталоги JavaScript у робочому просторі. Якщо їх немає, процес завершується з помилкою.
20
- 4. Перевіряється наявність усіх канонічних базових конфігураційних файлів. Якщо хоча б один відсутній, процес завершується з помилкою.
21
- 5. Для кожного знайденого кореневого каталогу JavaScript:
22
- а. Визначається, чи містить цей каталог файли Vue.
23
- б. Створюється або оновлюється конфігураційний файл Stryker для цього каталогу. Якщо файл відсутній, він копіюється з канонічного базового файлу, замінюючи ім'я конфігурації Vitest на фактичне ім'я конфігу каталогу. Якщо файл існує, він залишається без змін.
24
- в. Якщо каталог містить файли Vue і конфігураційний файл Stryker вже існував, виконується аугментація конфігурації. Це додає локальний плагін Vue-макросів до конфігурації Stryker, якщо він відсутній.
25
- г. Створюється або оновлюється конфігураційний файл плагіна Vue-макросів.
26
- д. Створюється або оновлюється конфігураційний файл Vitest для каталогу, використовуючи фактичне ім'я конфігу.
27
- 6. Виконується гарантія, що файли з результатами тестів Stryker та покриття (lcov) не потрапляють у систему контролю версій. Якщо додаються нові патерни до `.gitignore`, це фіксується як успіх (test.mdc).
28
- 7. Процес завершується з кодом виходу, що відображає успіх або помилку.
18
+ 2. Перевіряється, чи у конфігурації дозволено виконання перевірок для JavaScript. Якщо ні, процес завершується з кодом, що вказує на пропуск.
19
+ 3. Збираються всі кореневі каталоги, що містять JavaScript-проєкти. Якщо жоден не знайдено, процес завершується з повідомленням про помилку (test.mdc).
20
+ 4. Перевіряється наявність усіх канонічних файлів конфігурації (baseline) у системі. Якщо хоча б один відсутній, процес завершується з повідомленням про помилку.
21
+ 5. Для кожного знайденого кореневого каталогу проєкту виконується наступне:
22
+ а. Визначається, чи містить проєкт файли `.vue`.
23
+ б. Створюється або оновлюється файл `stryker.config.mjs` у корені проєкту, копіюючи відповідний канонічний baseline. У цьому файлі замінюється посилання на конфігурацію Vitest на фактичне ім'я конфігурації проєкту.
24
+ в. Якщо проєкт містить файли `.vue` і файл `stryker.config.mjs` вже існував, виконується аугментація цього файлу. Аугментація додає плагін `vue-macros` до конфігурації, якщо він відсутній, зберігаючи при цьому форматування користувача.
25
+ г. Створюється або оновлюється файл `stryker-vue-macros-ignorer.mjs` у корені проєкту.
26
+ д. Створюється або оновлюється файл конфігурації Vitest у корені проєкту, використовуючи відповідний канонічний baseline.
27
+ 6. Виконується гарантування, що файли, що містять артефакти тестів Stryker та покриття (наприклад, `**/reports/stryker/`, `**/coverage/`), додані до `.gitignore` у корені проєкту. Якщо додано нові записи, виводиться повідомлення (test.mdc).
28
+ 7. Процес завершується з кодом, що вказує на успішне виконання або на виявлені порушення.
29
29
 
30
30
  ## Гарантії поведінки
31
31
 
@@ -10,16 +10,22 @@ docgen:
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль визначає, чи встановлено режим `pool: 'forks'` у конфігураційному файлі Vitest. Перевірка здійснюється у `vitest.config.mjs` або `vitest.config.js` кореневого репозиторію. Це забезпечує коректну поведінку при паралельному запуску тестових файлів. Результат перевірки фіксується у test.mdc.
13
+ Визначає шлях до конфігураційного файлу Vitest. Перевіряє, чи встановлено в цьому файлі `pool: 'forks'`. (test.mdc)
14
+
15
+ Поведінка:
16
+
17
+ - Якщо конфігураційний файл не знайдено, завершує роботу з кодом, що вказує на помилку. (test.mdc)
18
+ - Якщо конфігураційний файл знайдено, але в ньому відсутній ключ `pool: 'forks'`, завершує роботу з кодом, що вказує на помилку. (test.mdc)
19
+ - Якщо конфігураційний файл знайдено і містить `pool: 'forks'`, завершує роботу з кодом 0. (test.mdc)
14
20
 
15
21
  ## Поведінка
16
22
 
17
- 1. Визначається назва конфігураційного файлу Vitest, шукаючи спочатку `vitest.config.mjs`, а потім `vitest.config.js` у корені репозиторію.
18
- 2. Якщо конфігураційний файл відсутній, виконується пропуск перевірки, і повертається код успіху.
19
- 3. Якщо конфігураційний файл знайдено, його вміст зчитується.
20
- 4. Перевіряється, чи містить вміст конфігураційного файлу рядок, що вказує на використання `pool: 'forks'`.
21
- 5. Якщо рядок знайдено, фіксується успіх (test.mdc).
22
- 6. Якщо рядок не знайдено, фіксується помилка (test.mdc), оскільки це необхідно для захисту від гонки у `process.cwd` між паралельними тестовими файлами.
23
+ 1. Визначається шлях до файлу конфігурації Vitest, шукаючи спочатку `vitest.config.mjs`, а потім `vitest.config.js` у корені репозиторію.
24
+ 2. Якщо конфігураційний файл відсутній, виконується пропуск перевірки, і повертається код виходу 0.
25
+ 3. Якщо файл знайдено, його вміст зчитується.
26
+ 4. Перевіряється, чи містить вміст конфігураційного файлу рядок, що вказує на встановлення `pool: 'forks'`.
27
+ 5. Якщо рядок знайдено, виконується успішне повідомлення (test.mdc).
28
+ 6. Якщо рядок не знайдено, виконується повідомлення про помилку (test.mdc), що вказує на необхідність додавання `pool: 'forks'` для захисту від гонки в умовах паралельного виконання тестів.
23
29
  7. Функція `check` повертає код виходу, що відображає результат перевірки (0 для успіху/пропуску, 1 для помилки).
24
30
 
25
31
  ## Публічний API
@@ -10,19 +10,20 @@ docgen:
10
10
 
11
11
  ## Огляд
12
12
 
13
- Копіює composite GitHub Action `setup-bun-deps` з каталогу `github-actions/setup-bun-deps/` у цільовий репозиторій (`.github/actions/setup-bun-deps/`). Це забезпечує можливість робочим процесам з правил `ga`, `js` та `text` викликати локально розміщений action (`uses: ./.github/actions/setup-bun-deps`) одразу після виконання `actions/checkout@v6`, використовуючи CLI `npx \@nitra/cursor`.
13
+ Копіює composite GitHub Action `setup-bun-deps` з каталогу `github-actions/setup-bun-deps/` у корені tarball пакету `@nitra/cursor` у цільовий репозиторій за шлях `.github/actions/setup-bun-deps/action.yml`. Це забезпечує можливість для workflow з правил `ga`, `js` або `text` викликати цей action для налаштування залежностей Bun одразу після виконання `actions/checkout@v6`.
14
14
 
15
15
  ## Поведінка
16
16
 
17
17
  1. Перевіряє наявність шаблону composite action у корені встановленого пакету `@nitra/cursor`.
18
- 2. Створює необхідну директорію для composite action у корені цільового репозиторію, ігноруючи шляхи `.github` та `.git`.
19
- 3. Зчитує вміст шаблону composite action.
20
- 4. Записує вміст шаблону у цільовий шлях composite action у корені цільового репозиторію, гарантуючи наявність завершального символу нового рядка.
18
+ 2. Створює необхідну директорію у корені цільового репозиторію для розміщення composite action.
19
+ 3. Зчитує вміст шаблону composite action з кореня встановленого пакету.
20
+ 4. Записує вміст шаблону composite action у цільовий шлях у корені репозиторію.
21
21
  5. Повертає підтвердження успішного запису та повний шлях до файлу.
22
+ 6. Не перевіряє шляхи `.github` чи `.git`.
22
23
 
23
24
  ## Публічний API
24
25
 
25
- syncSetupBunDepsAction — фіксує в `projectRoot` композитну дію з коренем встановленого `@nitra/cursor`.
26
+ syncSetupBunDepsAction — фіксує у `projectRoot` композитну дію, що вказує на корінь встановленого `@nitra/cursor`.
26
27
 
27
28
  ## Гарантії поведінки
28
29
 
@@ -3,305 +3,25 @@ type: JS Module
3
3
  title: inline-template-links.mjs
4
4
  resource: npm/scripts/lib/inline-template-links.mjs
5
5
  docgen:
6
- crc: 7726f0ef
6
+ crc: b659349c
7
+ model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
+ score: 100
7
9
  ---
8
10
 
9
- Модуль `inline-template-links.mjs` — допоміжна утиліта для **build-кроку обробки `.mdc`-правил**. Її роль: у текстовому вмісті `.mdc`-документа знайти Markdown-посилання, які ведуть на template-файли (шлях містить сегмент `/template/` або `/templates/`), і **замінити** ці посилання на **інлайн fenced-блоки** з фактичним вмістом target-файлу.
11
+ ## Огляд
10
12
 
11
- Простими словами: замість того щоб у згенерованому правилі читач бачив посилання `[конфіг](./templates/package.json.snippet.json)`, він побачить безпосередньо назву реального файлу (`package.json`) і fenced-блок із його вмістом це робить правило «самодостатнім», без потреби клікати по лінках.
13
+ Цей модуль реалізує механізми вбудовування контенту в текстові документи, використовуючи конфігурації з `package.json.snippet.json` та `package.json`. Він замінює посилання в тексті на вміст файлів-шаблонів, розташованих у каталозі правил, а також на вміст файлів з розширенням .mdc, які не є шаблонами. Функції дозволяють вставляти посилання на шаблони (`inlineTemplateLinks`) та включати вміст Markdown (`inlineMarkdownIncludes`).
12
14
 
13
- Особливості:
15
+ ## Поведінка
14
16
 
15
- - Працює асинхронно (`async`), бо читає файли через `node:fs/promises`.
16
- - Робить **fail-loud** валідацію: якщо посилання вказує на неіснуючий файл кидає `Error` (а не мовчки пропускає), щоб автор правила одразу побачив проблему.
17
- - «Розгортає» спеціальні суфікси `.snippet.<ext>` / `.deny.<ext>` / `.contains.<ext>` до імені реального target-файлу, який вони описують (наприклад `package.json.snippet.json` → `package.json`).
18
- - Безпечно щодо ReDoS: усі regexp — статичні літерали з обмеженням довжини, без `new RegExp(variable)` із користувацьких даних.
17
+ inlineTemplateLinks замінює посилання в тексті на вбудовані блоки з вмістом файлів, що містять шаблони, якщо ці файли знаходяться в каталозі правил.
18
+ inlineMarkdownIncludes замінює посилання в тексті на вміст файлів з розширенням .mdc, якщо ці файли не є шаблонами.
19
19
 
20
- Модуль експортує єдину функцію `inlineTemplateLinks(text, ruleDir)`.
20
+ ## Публічний API
21
21
 
22
- ## Експорти / API
22
+ inlineTemplateLinks Замінює посилання у Markdown, що містять `/template/`, на вбудовані блокові конструкції, зчитуючи вміст з вказаного файлу. Викидає помилку, якщо цільове посилання не знайдено.
23
+ inlineMarkdownIncludes — Замінює посилання у Markdown, що закінчуються на `.mdc` (і не є шляхом `/template/`), на сирий вміст відповідного файлу Markdown. Викидає помилку, якщо цільове посилання не знайдено.
23
24
 
24
- | Експорт | Тип | Призначення |
25
- | --------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
26
- | `inlineTemplateLinks` | `async function(text: string, ruleDir: string): Promise<string>` | Замінює Markdown-посилання на template-файли в `.mdc`-тексті на інлайн fenced-блоки з фактичним вмістом цих файлів. |
25
+ ## Гарантії поведінки
27
26
 
28
- Усі інші імена в модулі (`langFromExt`, `normalizeTargetName`, константи `MD_LINK_RE`, `TEMPLATE_SEGMENT_RE`, `SLOT_SUFFIX_RES`) — **внутрішні** (не експортуються).
29
-
30
- ## Внутрішні константи
31
-
32
- ### `MD_LINK_RE`
33
-
34
- ```js
35
- ;/\[([^\]]{1,200})\]\((\.\/[^)]{1,500})\)/g
36
- ```
37
-
38
- Глобальний regexp, який ловить **Markdown-посилання вигляду `[label](./path)`** із обов'язковим префіксом `./` у href. Group 1 — текст посилання (до 200 символів), group 2 — шлях (до 500 символів, що починається з `./`). Обмеження довжин — захист від ReDoS / pathological input.
39
-
40
- ### `TEMPLATE_SEGMENT_RE`
41
-
42
- ```js
43
- ;/\/templates?\//
44
- ```
45
-
46
- Перевіряє, чи шлях містить сегмент `/template/` або `/templates/`. Тільки такі посилання вважаються «template-посиланнями» і підлягають заміні; інші Markdown-лінки залишаються недоторканими.
47
-
48
- ### `SLOT_SUFFIX_RES`
49
-
50
- Масив із трьох **статичних** regexp:
51
-
52
- ```js
53
- ;[/^(.+)\.snippet\.[^.]+$/, /^(.+)\.deny\.[^.]+$/, /^(.+)\.contains\.[^.]+$/]
54
- ```
55
-
56
- Кожен ловить ім'я файлу з суфіксом-«слотом»: `<name>.snippet.<ext>`, `<name>.deny.<ext>`, `<name>.contains.<ext>`. Group 1 — це ім'я реального target-файлу (без суфікса слоту і без власного розширення). Коментар у коді явно зазначає: regexp-літерали статичні, без `RegExp(variable)`.
57
-
58
- ## Функції
59
-
60
- ### `langFromExt(filePath)` — internal
61
-
62
- Сигнатура:
63
-
64
- ```js
65
- function langFromExt(filePath: string): string
66
- ```
67
-
68
- Параметри:
69
-
70
- - `filePath` — рядок зі шляхом до файлу (досить навіть базового імені, бо використовується лише розширення).
71
-
72
- Повертає:
73
-
74
- - Рядок-ідентифікатор мови для Markdown fenced-блока:
75
- - `'json'` — якщо розширення `.json`;
76
- - `'toml'` — якщо `.toml`;
77
- - `'yaml'` — якщо `.yml` або `.yaml`;
78
- - `''` (порожній рядок) — для будь-яких інших розширень.
79
-
80
- Side effects: жодних — чиста функція над рядком.
81
-
82
- Призначення: визначити, який мовний таг ставити після відкривального ` ``` ` у згенерованому fenced-блоці, щоб підсвічування синтаксису працювало коректно.
83
-
84
- ### `normalizeTargetName(fileBasename)` — internal
85
-
86
- Сигнатура:
87
-
88
- ```js
89
- function normalizeTargetName(fileBasename: string): string
90
- ```
91
-
92
- Параметри:
93
-
94
- - `fileBasename` — базове ім'я файлу (без шляху), наприклад `package.json.snippet.json`.
95
-
96
- Повертає:
97
-
98
- - Якщо ім'я **збігається з одним із regexp у `SLOT_SUFFIX_RES`** (тобто має суфікс `.snippet.<ext>`, `.deny.<ext>` або `.contains.<ext>`) — повертається **group 1** першого збігу (ім'я без слоту). Приклади:
99
- - `package.json.snippet.json` → `package.json`
100
- - `eslint.config.js.deny.js` → `eslint.config.js`
101
- - `Caddyfile.contains.txt` → `Caddyfile`
102
- - Якщо жоден з regexp не збігся — повертається оригінальне `fileBasename` без змін.
103
-
104
- Side effects: відсутні.
105
-
106
- Призначення: для template-файлу з суфіксом-слотом відновити **реальне ім'я target-файлу**, на який цей template посилається; саме це ім'я потім підставляється як заголовок перед fenced-блоком у згенерованому Markdown.
107
-
108
- Коментар над функцією у вихіднику прямо описує цю поведінку: «Strip `.<slot>.<ext>` suffix (slot ∈ snippet/deny/contains) to recover the real target file name».
109
-
110
- ### `inlineTemplateLinks(text, ruleDir)` — **exported**
111
-
112
- Сигнатура:
113
-
114
- ```js
115
- export async function inlineTemplateLinks(
116
- text: string,
117
- ruleDir: string,
118
- ): Promise<string>
119
- ```
120
-
121
- Параметри:
122
-
123
- - `text` — вміст `.mdc`-файлу (повний текст) як рядок.
124
- - `ruleDir` — **абсолютний** шлях до директорії правила (наприклад `.../npm/rules/security/`). Усі відносні href із `./` резолвляться **відносно цього каталогу**.
125
-
126
- Повертає:
127
-
128
- - `Promise<string>` — трансформований текст, у якому всі **template-посилання** замінено на блоки виду:
129
-
130
- ````text
131
- `<targetName>`:
132
-
133
- ```<lang>
134
- <contents>
135
- ````
136
-
137
- ```
138
-
139
- де `targetName` — результат `normalizeTargetName(basename(absPath))`, `lang` — результат `langFromExt(absPath)`, а `contents` — вміст файлу після `.trim()`.
140
-
141
- ```
142
-
143
- - Якщо у тексті немає жодного template-посилання — повертається **той самий `text` без змін** (early-exit).
144
-
145
- Алгоритм роботи:
146
-
147
- 1. Знаходимо **всі** збіги `MD_LINK_RE` у `text` через `text.matchAll(...)`.
148
- 2. Фільтруємо їх: залишаємо лише ті, у яких href (group 2) містить `/template/` або `/templates/` (через `TEMPLATE_SEGMENT_RE.test(m[2])`).
149
- 3. Якщо після фільтрації збігів **немає** — повертаємо `text` як є.
150
- 4. Кладемо стартовий результат `result = text`.
151
- 5. Для кожного збігу послідовно (`for ... of`, з `await` на читанні файлу):
152
- 1. Деструктуруємо: `const [fullMatch, , href] = match` (label не використовується, тому позиція пропущена).
153
- 2. Будуємо відносний шлях: `relPath = href.slice(2)` — обрізаємо префікс `./` (його гарантує regexp).
154
- 3. Збираємо абсолютний шлях: `absPath = join(ruleDir, relPath)`.
155
- 4. Перевіряємо існування: `existsSync(absPath)`. Якщо файлу немає — **кидаємо** `Error`:
156
-
157
- ```text
158
- inlineTemplateLinks: file not found: <absPath> (referenced from .mdc)
159
- ```
160
-
161
- Жодного fallback / тихого пропуску — це fail-loud за дизайном.
162
-
163
- 5. Читаємо файл: `raw = await readFile(absPath, 'utf8')`, далі `contents = raw.trim()` (прибираємо хвостові пробіли / переноси).
164
- 6. Обчислюємо `lang = langFromExt(absPath)`.
165
- 7. Обчислюємо `targetName = normalizeTargetName(basename(absPath))`.
166
- 8. Формуємо `replacement` — backtick-екранований заголовок, порожній рядок і fenced-блок із `lang`:
167
-
168
- ```js
169
- ;`\`${targetName}\`:\n\n\`\`\`${lang}\n${contents}\n\`\`\``
170
- ```
171
-
172
- 9. Робимо заміну: `result = result.replace(fullMatch, () => replacement)`. Передача **callback-форми** в `.replace` критично важлива: інакше спецсимволи у `replacement` (наприклад `$&`, `$1` із вмісту шаблону) трактувалися б як backreferences і зламали б вивід.
173
-
174
- 6. Повертаємо `result`.
175
-
176
- Side effects:
177
-
178
- - **Читання** файлів із диска (синхронна перевірка `existsSync` + асинхронне `readFile`).
179
- - **Кидання `Error`** при відсутності target-файлу — це навмисна поведінка («fail loud — user must know»), а не баг.
180
- - Запису на диск або мережевих викликів **не робить**.
181
-
182
- Складність та обмеження:
183
-
184
- - Цикл лінійний за кількістю template-посилань у тексті; для кожного — один `existsSync` і один `readFile`.
185
- - Файли читаються **послідовно** (через `await` у тілі `for...of`), а не паралельно через `Promise.all`. Це осмислений вибір: правил, як правило, мало, а послідовність робить порядок помилок передбачуваним.
186
- - Заміна виконується через простий `result.replace(fullMatch, ...)` — перший збіг `fullMatch` у `result`. Якщо однакове Markdown-посилання трапляється кілька разів — модифікується лише перше входження (фактичний `matchAll` дасть і інші входження, але кожен з них має той самий `fullMatch`, і їх теж замінить — по одному за крок ітерації; для повних дублікатів це працює коректно).
187
-
188
- ## Залежності
189
-
190
- ### Стандартна бібліотека Node.js
191
-
192
- - `node:fs` → `existsSync` — синхронна перевірка наявності файлу перед читанням.
193
- - `node:fs/promises` → `readFile` — асинхронне читання вмісту target-файлу як UTF-8.
194
- - `node:path` → `basename`, `extname`, `join` — робота з шляхами:
195
- - `extname` — у `langFromExt` для визначення мови;
196
- - `basename` — для отримання базового імені файлу, з якого `normalizeTargetName` витягне target-ім'я;
197
- - `join` — для побудови абсолютного шляху від `ruleDir` + `relPath`.
198
-
199
- ### Зовнішні залежності
200
-
201
- Жодних npm-пакетів. Модуль працює лише на Node.js стандарті.
202
-
203
- ### Споживачі модуля
204
-
205
- Файл лежить у `npm/scripts/lib/` поряд із іншими допоміжними утилітами для збірки правил, тому очікувані споживачі — build-скрипти у `npm/scripts/`, які генерують підсумкові `.mdc`-документи для cursor-rules / Claude-rules. Експортована функція `inlineTemplateLinks` викликається на проміжній стадії пайплайна обробки тексту `.mdc`-файлу разом із `ruleDir`, обчисленим від шляху до самого `.mdc`.
206
-
207
- ## Потік виконання / Використання
208
-
209
- Типовий сценарій інтеграції в build-скрипт:
210
-
211
- ```js
212
- import { readFile, writeFile } from 'node:fs/promises'
213
- import { dirname } from 'node:path'
214
-
215
- import { inlineTemplateLinks } from './lib/inline-template-links.mjs'
216
-
217
- const mdcPath = '/abs/path/to/npm/rules/security/n-security.mdc'
218
- const original = await readFile(mdcPath, 'utf8')
219
-
220
- const ruleDir = dirname(mdcPath) // важливо: каталог, де лежить .mdc
221
- const transformed = await inlineTemplateLinks(original, ruleDir)
222
-
223
- await writeFile(mdcPath, transformed, 'utf8')
224
- ```
225
-
226
- Що відбувається крок-за-кроком на прикладі.
227
-
228
- Вхідний `.mdc`-фрагмент (`ruleDir = .../npm/rules/security/`):
229
-
230
- ```text
231
- Snippet вимоги до `package.json` — див. [тут](./templates/package.json.snippet.json).
232
- ```
233
-
234
- Файл `.../npm/rules/security/templates/package.json.snippet.json`:
235
-
236
- ```json
237
- {
238
- "scripts": {
239
- "lint": "eslint ."
240
- }
241
- }
242
- ```
243
-
244
- Що зробить `inlineTemplateLinks`:
245
-
246
- 1. `matchAll(MD_LINK_RE)` знайде один збіг із href `./templates/package.json.snippet.json`.
247
- 2. `TEMPLATE_SEGMENT_RE` пропустить його (бо є `/templates/`).
248
- 3. `relPath = 'templates/package.json.snippet.json'`, `absPath = '.../npm/rules/security/templates/package.json.snippet.json'`.
249
- 4. `existsSync(absPath)` → `true`, файл читається.
250
- 5. `langFromExt(absPath)` → `'json'`.
251
- 6. `normalizeTargetName('package.json.snippet.json')` → `'package.json'` (спрацює regexp `/^(.+)\.snippet\.[^.]+$/`).
252
- 7. `replacement` буде:
253
-
254
- ````text
255
- `package.json`:
256
-
257
- ```json
258
- {
259
- "scripts": {
260
- "lint": "eslint ."
261
- }
262
- }
263
- ````
264
-
265
- ```
266
-
267
- ```
268
-
269
- 8. Результат заміняє оригінальний Markdown-лінк у тексті.
270
-
271
- Випадки помилок:
272
-
273
- - Якщо `href` веде на неіснуючий файл — кидається `Error` із повним абсолютним шляхом у повідомленні; build-скрипт має право або впасти, або зловити цю помилку.
274
- - Якщо у `text` немає Markdown-посилань або жодне з них не містить `/template(s)/` — функція повертає `text` без модифікацій.
275
- - Якщо template-файл має нерозпізнаване розширення (наприклад `.txt` або `.conf`) — `langFromExt` поверне порожній рядок, і fenced-блок буде без мовного тегу (Markdown це допускає).
276
- - Якщо ім'я template-файлу **не** має одного з суфіксів `.snippet.<ext>` / `.deny.<ext>` / `.contains.<ext>` — `normalizeTargetName` поверне його як є; це нормальна поведінка для «звичайних» template-файлів, у яких саме ім'я і є target-ім'ям.
277
-
278
- ## Rebuild Test
279
-
280
- Якщо видалити цей файл і відтворити його з нуля, мінімально достатній рецепт такий:
281
-
282
- 1. Створи модуль `inline-template-links.mjs` у `npm/scripts/lib/`.
283
- 2. Імпортуй з `node:fs` функцію `existsSync`, з `node:fs/promises` — `readFile`, з `node:path` — `basename`, `extname`, `join`.
284
- 3. Оголоси константи:
285
- - `MD_LINK_RE = /\[([^\]]{1,200})\]\((\.\/[^)]{1,500})\)/g` — глобальний regexp для Markdown-посилань `[label](./path)`.
286
- - `TEMPLATE_SEGMENT_RE = /\/templates?\//` — фільтр шляхів, що містять `/template/` чи `/templates/`.
287
- - `SLOT_SUFFIX_RES` — масив із трьох **статичних** regexp: `/^(.+)\.snippet\.[^.]+$/`, `/^(.+)\.deny\.[^.]+$/`, `/^(.+)\.contains\.[^.]+$/`. Принципово: жодного `new RegExp(variable)` — захист від ReDoS.
288
- 4. Реалізуй `langFromExt(filePath)`:
289
- - `extname(filePath)` → за вмістом повернути `'json' | 'toml' | 'yaml' | ''` (для `.yml` теж `'yaml'`).
290
- 5. Реалізуй `normalizeTargetName(fileBasename)`:
291
- - Пройди `SLOT_SUFFIX_RES` у заданому порядку; при першому збігу поверни `match[1]`. Інакше — оригінал.
292
- 6. Експортуй `async function inlineTemplateLinks(text, ruleDir)`:
293
- - `matchAll(MD_LINK_RE)` → відфільтруй за `TEMPLATE_SEGMENT_RE.test(href)`.
294
- - Якщо нічого не залишилося — поверни `text`.
295
- - Для кожного збігу: `relPath = href.slice(2)`, `absPath = join(ruleDir, relPath)`; якщо `!existsSync(absPath)` — `throw new Error('inlineTemplateLinks: file not found: <absPath> (referenced from .mdc)')`.
296
- - Читай файл `utf8`, роби `.trim()`, обчисли `lang` і `targetName`, побудуй `replacement = \`\\\`${targetName}\\\`:\\n\\n\\\`\\\`\\\`${lang}\\n${contents}\\n\\\`\\\`\\\``.
297
- - Заміни через `result = result.replace(fullMatch, () => replacement)` (саме callback-форма — щоб уникнути інтерпретації `$&`/`$1` у вмісті template-файлу).
298
- 7. Поверни `result`.
299
-
300
- Контракт, який має зберегтися:
301
-
302
- - Чиста функція над текстом + читання файлів (без записів і без мережі).
303
- - **Fail-loud** на відсутній target.
304
- - Підтримка трьох слот-суфіксів: `snippet`, `deny`, `contains`.
305
- - Підтримка мов підсвічування: `json`, `toml`, `yaml`, інакше — без таргу.
306
- - Жодного `RegExp(variable)`.
307
- - Префікс href повинен починатися з `./`, інакше посилання ігнорується (це закладено в `MD_LINK_RE`).
27
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -3,31 +3,31 @@ type: JS Module
3
3
  title: mirror-parity.mjs
4
4
  resource: npm/scripts/lib/mirror-parity.mjs
5
5
  docgen:
6
- crc: d2140a00
6
+ crc: 5e366df1
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 90
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Модуль забезпечує паралельність (parity) дзеркал правил. Він порівнює вміст дзеркала `.cursor/rules/n-<id>.mdc` з канонічним вмістом `npm/rules/<id>/<id>.mdc`, який містить вбудовані шаблони (inlined-шаблони). Це гарантує, що локальні копії правил відповідають централізованим, трансформованим версіям, виявляючи дрейф, що виникає при зміні канонічного `.mdc` без регенерації дзеркала.
13
+ Модуль порівнює вміст дзеркал правил (`.cursor/rules/n-<id>.mdc`) з канонічними версіями (`npm/rules/<id>/<id>.mdc`), які містять застосовані трансформації (трансформ, що застосовує `readBundledRuleContent` $\to$ `inlineTemplateLinks`). Він визначає список керованих дзеркал, формує очікуваний вміст для кожного дзеркала та виявляє дрейф відхилення фактичного вмісту від очікуваного.
14
14
 
15
15
  ## Поведінка
16
16
 
17
17
  listManagedMirrors
18
- Визначає список керованих дзеркал правил, які мають канонічне джерело у `npm/rules`.
18
+ Визначає список керованих дзеркал правил, які мають відповідне канонічне джерело у `npm/rules`.
19
19
 
20
20
  expectedMirrorContent
21
- Формує очікуваний вміст дзеркала, застосовуючи трансформацію до канонічного вмісту.
21
+ Формує очікуваний вміст дзеркала, застосовуючи трансформації до канонічного файлу.
22
22
 
23
23
  findMirrorDrift
24
- Виявляє і повертає список ідентифікаторів дзеркал, вміст яких відрізняється від очікуваного канонічного.
24
+ Виявляє ідентифікатори дзеркал, чий фактичний вміст відрізняється від очікуваного вмісту, отриманого з канону.
25
25
 
26
26
  ## Публічний API
27
27
 
28
- listManagedMirrors — перераховує дзеркала, які мають визначене основне джерело, і ігнорує зовнішні.
29
- expectedMirrorContent — визначає, яким має бути вміст дзеркала, використовуючи основне джерело з вбудованими шаблонами.
30
- findMirrorDrift — виявляє ідентифікатори дзеркал, чий фактичний вміст відрізняється від очікуваного.
28
+ listManagedMirrors — перераховує керовані дзеркала, які мають визначене канонічне джерело.
29
+ expectedMirrorContent — визначає бажаний вміст дзеркала, використовуючи канон із вбудованими шаблонами.
30
+ findMirrorDrift — знаходить ідентифікатори дзеркал, у яких фактичний вміст відрізняється від очікуваного.
31
31
 
32
32
  ## Гарантії поведінки
33
33
 
@@ -5,22 +5,22 @@ resource: npm/scripts/lib/timing-summary.mjs
5
5
  docgen:
6
6
  crc: 47660e16
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 95
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Формує рядок, що відображає тривалість у форматі `<ціла>.<десята>s`, забезпечуючи стабільну одиницю вимірювання для всіх інтервалів. Генерує звіт про час виконання для orchestrator `fix` / `lint`, який включає деталі кожного вимірювання та загальний час. Звіт містить маркер `❌` на рядку, якщо відповідний вимірник не пройшов успішно. Дані для звітів отримуються з `package.json` та використовуються у точках виклику `runFixCommand` у `bin/n-cursor.js` та `runLintCli` у `scripts/lib/run-lint-cli.mjs`. Функція є чистою, не виконує I/O, і повертає готовий рядок з фінальним `\n`, друк якого здійснюється на стороні виклику.
13
+ Форматує тривалість у форматі `<ціла>.<десята>s`. Генерує таблицю-резюме часу виконання для оркестратора `fix` або `lint`, яка включає детальні записи та загальний час прогону. Таблиця містить маркер `❌` для позначення невдач. Функція повертає готовий рядок із фінальним `\n`; друк здійснюється на стороні виклику.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- formatDurationMs форматує тривалість у мілісекундах як рядок у форматі `<ціла>.<десята>s`.
18
- formatTimingSummary генерує багаторядковий текстовий звіт про час виконання, включаючи деталі кожного запису та загальний час.
17
+ formatDurationMs форматує тривалість у мілісекундах у рядок у форматі `<ціла>.<десята>s`.
18
+ formatTimingSummary генерує багаторядковий рядок із таблицею-резюме часу виконання, включаючи окремі записи та загальний час.
19
19
 
20
20
  ## Публічний API
21
21
 
22
- ⏱ formatDurationMs: Перетворює мілісекунди у формат `<sec>.<десята>s`, використовуючи округлення вниз.
23
- ⏱ formatTimingSummary: Генерує багаторядковий вивід таблиці-резюме часу виконання.
22
+ ⏱ formatDurationMs: Перетворює мілісекунди у формат `<sec>.<десята>s`, використовуючи нижнє округлення для забезпечення консистентності виводу.
23
+ ⏱ formatTimingSummary: Генерує багаторядковий текстовий звіт про час виконання, структурований як таблиця з сумарними даними.
24
24
 
25
25
  ## Гарантії поведінки
26
26
 
@@ -4,6 +4,7 @@ import { basename, extname, join } from 'node:path'
4
4
 
5
5
  const MD_LINK_RE = /\[([^\]]{1,200})\]\((\.\/[^)]{1,500})\)/g
6
6
  const TEMPLATE_SEGMENT_RE = /\/templates?\//
7
+ const MDC_EXT_RE = /\.mdc$/
7
8
  /** Статичні regexp-літерали `^(.+)\.<slot>\.<ext>$` — без `RegExp(variable)`. */
8
9
  const SLOT_SUFFIX_RES = [/^(.+)\.snippet\.[^.]+$/, /^(.+)\.deny\.[^.]+$/, /^(.+)\.contains\.[^.]+$/]
9
10
 
@@ -66,3 +67,33 @@ export async function inlineTemplateLinks(text, ruleDir) {
66
67
 
67
68
  return result
68
69
  }
70
+
71
+ /**
72
+ * Finds markdown links whose href ends with `.mdc` (and is not a /template/ path) and
73
+ * replaces them with the raw markdown content of the linked file (no fencing).
74
+ * Intended for per-concern section files living alongside their .mjs implementations.
75
+ * Throws Error if a matched link target doesn't exist (fail loud).
76
+ * @param {string} text .mdc file contents (after inlineTemplateLinks)
77
+ * @param {string} ruleDir absolute path to the rule directory
78
+ * @returns {Promise<string>} transformed text
79
+ */
80
+ export async function inlineMarkdownIncludes(text, ruleDir) {
81
+ const matches = [...text.matchAll(MD_LINK_RE)].filter(m => MDC_EXT_RE.test(m[2]) && !TEMPLATE_SEGMENT_RE.test(m[2]))
82
+ if (matches.length === 0) return text
83
+
84
+ let result = text
85
+ for (const match of matches) {
86
+ const [fullMatch, , href] = match
87
+ const relPath = href.slice(2) // strip leading ./
88
+ const absPath = join(ruleDir, relPath)
89
+
90
+ if (!existsSync(absPath)) {
91
+ throw new Error(`inlineMarkdownIncludes: file not found: ${absPath} (referenced from .mdc)`)
92
+ }
93
+
94
+ const raw = await readFile(absPath, 'utf8')
95
+ result = result.replace(fullMatch, () => raw.trim())
96
+ }
97
+
98
+ return result
99
+ }
@@ -9,7 +9,7 @@
9
9
  import { existsSync, readdirSync, readFileSync } from 'node:fs'
10
10
  import { dirname, join } from 'node:path'
11
11
 
12
- import { inlineTemplateLinks } from './inline-template-links.mjs'
12
+ import { inlineMarkdownIncludes, inlineTemplateLinks } from './inline-template-links.mjs'
13
13
 
14
14
  const MIRROR_PREFIX = 'n-'
15
15
  const MDC_EXT = '.mdc'
@@ -41,8 +41,10 @@ export function listManagedMirrors(repoRoot) {
41
41
  * @param {string} canonicalPath абсолютний шлях `npm/rules/<id>/<id>.mdc`
42
42
  * @returns {Promise<string>} очікуваний текст дзеркала
43
43
  */
44
- export function expectedMirrorContent(canonicalPath) {
45
- return inlineTemplateLinks(readFileSync(canonicalPath, 'utf8'), dirname(canonicalPath))
44
+ export async function expectedMirrorContent(canonicalPath) {
45
+ const dir = dirname(canonicalPath)
46
+ const withTemplates = await inlineTemplateLinks(readFileSync(canonicalPath, 'utf8'), dir)
47
+ return inlineMarkdownIncludes(withTemplates, dir)
46
48
  }
47
49
 
48
50
  /**
@@ -10,17 +10,17 @@ docgen:
10
10
 
11
11
  ## Огляд
12
12
 
13
- Визначає корінь JS-коду для проєктів, використовуючи `package.json` та `.n-cursor.json` як конфігураційні файли. Функція `resolveJsRoot` знаходить перший workspace (з підтримкою glob-патернів типу `cf/*`) для workspace-проєктів або корінь поточної директорії для single-package. Функція `resolveAllJsRoots` знаходить усі відповідні шляхи. Код свідомо ігнорує шляхи `.git` та `node_modules`. Ця утиліта є спільною для coverage-провайдера JS та test-концерну stryker_config (DRY).
13
+ Визначає корінь JS-коду в проєкті, відповідно до логіки для workspace-projects (перший workspace з підтримкою glob-патернів `cf/*`) або single-package (корінь cwd). Ця утиліта є спільною для coverage-провайдера JS та test-концерну `stryker_config` (DRY). Публічні функції дозволяють отримати один або повний список шляхів до всіх JS-коренів, при цьому свідомо виключаються каталоги `.git` та `node_modules`. Код спирається на конфігураційні файли `package.json` та `.n-cursor.json`.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- resolveJsRoot повертає абсолютний шлях до першого JS-кореня проєкту, якщо він існує, або null, якщо кореневий package.json відсутній.
18
- resolveAllJsRoots повертає масив абсолютних шляхів до всіх JS-коренів проєкту, враховуючи визначення `workspaces` у кореневому package.json, ігноруючи каталоги `.git` та `node_modules`.
17
+ resolveJsRoot повертає абсолютний шлях до першого JS-кореня проєкту. Якщо кореневий `package.json` відсутній, повертає null.
18
+ resolveAllJsRoots повертає масив абсолютних шляхів до всіх JS-коренів проєкту. Ігнорує каталоги `.git` та `node_modules`.
19
19
 
20
20
  ## Публічний API
21
21
 
22
22
  resolveJsRoot — знаходить кореневий каталог JavaScript-проєкту.
23
- resolveAllJsRoots — повертає шляхи до коренів усіх JavaScript-проєктів у робочому просторі.
23
+ resolveAllJsRoots — повертає всі каталоги JavaScript-проєктів у робочому просторі.
24
24
 
25
25
  ## Гарантії поведінки
26
26
 
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {}
2
+ export {};