@nitra/cursor 5.1.0 → 5.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.
Files changed (214) hide show
  1. package/.claude-template/settings.template.json +22 -0
  2. package/.pi-template/extensions/n-cursor-adr/docs/index.md +15 -9
  3. package/CHANGELOG.md +18 -1
  4. package/bin/n-cursor.js +73 -16
  5. package/docs/stryker.config.md +6 -0
  6. package/docs/vitest.config.md +6 -0
  7. package/lib/docs/llm.md +29 -0
  8. package/lib/docs/models.md +24 -17
  9. package/lib/docs/omlx.md +32 -0
  10. package/lib/llm.mjs +137 -0
  11. package/lib/omlx.mjs +49 -4
  12. package/package.json +1 -1
  13. package/rules/abie/docs/fix.md +6 -0
  14. package/rules/abie/js/docs/applies.md +6 -0
  15. package/rules/abie/js/docs/env_dns.md +25 -22
  16. package/rules/abie/js/docs/firebase_hosting.md +6 -0
  17. package/rules/abie/js/docs/hc_pairing.md +21 -25
  18. package/rules/abie/js/docs/ua_http_route.md +27 -19
  19. package/rules/abie/js/docs/ua_node_selector.md +24 -19
  20. package/rules/abie/lib/docs/enabled.md +13 -7
  21. package/rules/abie/lib/docs/env-dns.md +9 -3
  22. package/rules/abie/lib/docs/hc-yaml.md +6 -0
  23. package/rules/abie/lib/docs/http-route.md +6 -0
  24. package/rules/abie/lib/docs/k8s-tree.md +6 -0
  25. package/rules/abie/lib/docs/kustomization-patches.md +6 -0
  26. package/rules/abie/lib/docs/overlay-paths.md +6 -0
  27. package/rules/abie/lib/docs/yaml.md +6 -0
  28. package/rules/adr/docs/fix.md +6 -0
  29. package/rules/adr/js/docs/hooks.md +29 -244
  30. package/rules/bun/docs/fix.md +6 -0
  31. package/rules/bun/js/docs/layout.md +37 -375
  32. package/rules/capacitor/docs/fix.md +22 -108
  33. package/rules/capacitor/js/docs/platforms.md +62 -268
  34. package/rules/changelog/docs/fix.md +6 -0
  35. package/rules/changelog/js/docs/consistency.md +36 -383
  36. package/rules/changelog/lib/docs/package-manifest.md +6 -0
  37. package/rules/ci4/docs/fix.md +23 -165
  38. package/rules/ci4/js/docs/marksman_config.md +9 -1
  39. package/rules/docker/docs/fix.md +6 -0
  40. package/rules/docker/js/docs/lint.md +55 -239
  41. package/rules/docker/lib/docs/docker-hadolint.md +6 -0
  42. package/rules/docker/lib/docs/docker-mirror.md +6 -0
  43. package/rules/docker/lib/docs/docker-native-addon.md +6 -0
  44. package/rules/docker/lib/docs/docker-nginx-user.md +6 -0
  45. package/rules/docker/lint/docs/lint.md +9 -1
  46. package/rules/efes/docs/fix.md +6 -0
  47. package/rules/feedback/docs/fix.md +21 -131
  48. package/rules/ga/docs/fix.md +14 -12
  49. package/rules/ga/js/docs/lint.md +12 -9
  50. package/rules/ga/js/docs/workflows.md +20 -19
  51. package/rules/ga/lint/docs/lint.md +6 -0
  52. package/rules/graphql/docs/fix.md +6 -0
  53. package/rules/graphql/js/docs/tooling.md +18 -253
  54. package/rules/graphql/lib/docs/graphql-gql-scan.md +6 -0
  55. package/rules/hasura/docs/fix.md +18 -111
  56. package/rules/image-avif/docs/fix.md +6 -0
  57. package/rules/image-avif/js/docs/avif_generation.md +6 -0
  58. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +9 -3
  59. package/rules/js-bun-redis/lib/docs/redis-imports.md +6 -0
  60. package/rules/js-lint/js/docs/utils_imports.md +6 -0
  61. package/rules/js-lint-ci/docs/fix.md +7 -1
  62. package/rules/js-mssql/docs/fix.md +6 -0
  63. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +6 -0
  64. package/rules/js-run/docs/fix.md +6 -0
  65. package/rules/js-run/lib/docs/bunyan-imports.md +6 -0
  66. package/rules/js-run/lib/docs/check-env-scan.md +6 -0
  67. package/rules/js-run/lib/docs/conn-file-rules.md +6 -0
  68. package/rules/js-run/lib/docs/conn-imports-scan.md +6 -0
  69. package/rules/js-run/lib/docs/promise-settimeout-scan.md +6 -0
  70. package/rules/js-run/lib/docs/temporal-scan.md +6 -0
  71. package/rules/k8s/docs/fix.md +6 -0
  72. package/rules/k8s/lint/docs/lint.md +6 -0
  73. package/rules/nginx-default-tpl/docs/fix.md +6 -0
  74. package/rules/npm-module/js/docs/header_doc_pointer.md +7 -0
  75. package/rules/npm-module/js/header_doc_pointer.mjs +2 -8
  76. package/rules/php/docs/fix.md +6 -0
  77. package/rules/php/lint/docs/lint.md +6 -0
  78. package/rules/python/docs/fix.md +6 -0
  79. package/rules/python/lint/docs/lint.md +6 -0
  80. package/rules/rego/lint/docs/lint.md +6 -0
  81. package/rules/release/docs/change.md +6 -0
  82. package/rules/release/docs/fix.md +6 -0
  83. package/rules/release/docs/release.md +6 -0
  84. package/rules/release/lib/docs/aggregate.md +6 -0
  85. package/rules/release/lib/docs/change-file.md +6 -0
  86. package/rules/release/lib/docs/fallback.md +6 -0
  87. package/rules/rust/lib/docs/has-cargo-toml.md +6 -0
  88. package/rules/security/docs/fix.md +7 -1
  89. package/rules/security/js/docs/lint.md +6 -0
  90. package/rules/style-lint/docs/fix.md +6 -0
  91. package/rules/tauri/docs/fix.md +6 -0
  92. package/rules/test/docs/fix.md +6 -0
  93. package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +6 -0
  94. package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +6 -0
  95. package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +6 -0
  96. package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +6 -0
  97. package/rules/text/docs/fix.md +6 -0
  98. package/rules/text/lint/docs/lint.md +6 -0
  99. package/rules/text/lint/docs/run-dotenv-linter.md +6 -0
  100. package/rules/text/lint/docs/run-shellcheck.md +6 -0
  101. package/rules/text/lint/docs/run-v8r.md +6 -0
  102. package/rules/vue/lib/docs/vue-forbidden-imports.md +6 -0
  103. package/scripts/coverage-classify/cache.mjs +1 -1
  104. package/scripts/coverage-classify/docs/apply.md +6 -0
  105. package/scripts/coverage-classify/docs/cache.md +6 -0
  106. package/scripts/coverage-classify/docs/prompt.md +6 -0
  107. package/scripts/coverage-classify/docs/verdict-schema.md +6 -0
  108. package/scripts/coverage-classify/prompt.mjs +1 -1
  109. package/scripts/coverage-fix-extract.mjs +1 -1
  110. package/scripts/coverage-fix.mjs +2 -1
  111. package/scripts/docs/auto-skills.md +6 -0
  112. package/scripts/docs/build-agents-commands.md +7 -1
  113. package/scripts/docs/cli-entry.md +6 -0
  114. package/scripts/docs/coverage-fix-extract.md +6 -0
  115. package/scripts/docs/coverage-fix.md +6 -0
  116. package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +6 -0
  117. package/scripts/docs/lint-cli.md +6 -0
  118. package/scripts/docs/post-tool-use-fix.md +6 -0
  119. package/scripts/docs/rename-yaml-extensions.md +6 -0
  120. package/scripts/docs/skills-cli.md +6 -0
  121. package/scripts/docs/sync-setup-bun-deps-action.md +6 -0
  122. package/scripts/docs/upgrade-nitra-cursor-and-install.md +6 -0
  123. package/scripts/docs/worktree-cli.md +6 -0
  124. package/scripts/lib/docs/assert-project-root.md +6 -0
  125. package/scripts/lib/docs/check-mdc-template-refs.md +6 -0
  126. package/scripts/lib/docs/check-reporter.md +6 -0
  127. package/scripts/lib/docs/diff-added-lines.md +6 -0
  128. package/scripts/lib/docs/discover-check-rules-from-cursor.md +6 -0
  129. package/scripts/lib/docs/discover-checkable-rules.md +6 -0
  130. package/scripts/lib/docs/ensure-tool.md +6 -0
  131. package/scripts/lib/docs/generated-markdown.md +6 -0
  132. package/scripts/lib/docs/gha-workflow.md +6 -0
  133. package/scripts/lib/docs/inline-template-links.md +6 -0
  134. package/scripts/lib/docs/list-rule-ids.md +6 -0
  135. package/scripts/lib/docs/load-cursor-config.md +6 -0
  136. package/scripts/lib/docs/mirror-parity.md +6 -0
  137. package/scripts/lib/docs/read-n-cursor-config-lite.md +6 -0
  138. package/scripts/lib/docs/resolve-target-files.md +6 -0
  139. package/scripts/lib/docs/root-notice.md +6 -0
  140. package/scripts/lib/docs/rule-meta-helpers.md +6 -0
  141. package/scripts/lib/docs/rule-meta.md +6 -0
  142. package/scripts/lib/docs/run-conftest-batch.md +6 -0
  143. package/scripts/lib/docs/run-lint-step.md +6 -0
  144. package/scripts/lib/docs/run-rule-cli.md +6 -0
  145. package/scripts/lib/docs/run-rule.md +6 -0
  146. package/scripts/lib/docs/run-standard-lint.md +6 -0
  147. package/scripts/lib/docs/run-standard-rule.md +6 -0
  148. package/scripts/lib/docs/skill-meta.md +6 -0
  149. package/scripts/lib/docs/template.md +6 -0
  150. package/scripts/lib/docs/timing-summary.md +6 -0
  151. package/scripts/lib/docs/workspaces.md +6 -0
  152. package/scripts/lib/docs/worktree-notice.md +6 -0
  153. package/scripts/lib/docs/worktree.md +6 -0
  154. package/scripts/lib/mirror-parity.mjs +1 -1
  155. package/scripts/lib/root-notice.mjs +1 -1
  156. package/scripts/lib/worktree-notice.mjs +5 -5
  157. package/scripts/lib/worktree.mjs +1 -1
  158. package/scripts/sync-claude-config.mjs +3 -0
  159. package/scripts/utils/docs/ast-scan-utils.md +6 -0
  160. package/scripts/utils/docs/ensure-gitignore-entries.md +6 -0
  161. package/scripts/utils/docs/find-package-json-paths.md +6 -0
  162. package/scripts/utils/docs/lock-cache-dir.md +6 -0
  163. package/scripts/utils/docs/pass.md +6 -0
  164. package/scripts/utils/docs/resolve-cargo-manifest.md +6 -0
  165. package/scripts/utils/docs/resolve-cmd.md +6 -0
  166. package/scripts/utils/docs/resolve-js-root.md +6 -0
  167. package/scripts/utils/docs/test-helpers.md +6 -0
  168. package/scripts/utils/docs/walk-cache.md +6 -0
  169. package/scripts/utils/docs/walkDir.md +6 -0
  170. package/scripts/utils/docs/worktree-fingerprint.md +6 -0
  171. package/scripts/utils/resolve-js-root.mjs +1 -1
  172. package/skills/doc-aggregate/SKILL.md +129 -0
  173. package/skills/doc-aggregate/js/docgen-ignore.mjs +9 -0
  174. package/skills/{docgen → doc-aggregate}/js/docgen-scan.mjs +22 -67
  175. package/skills/doc-aggregate/js/docs/docgen-ignore.md +21 -0
  176. package/skills/doc-files/SKILL.md +100 -0
  177. package/skills/doc-files/js/docgen-crc.mjs +164 -0
  178. package/skills/{docgen → doc-files}/js/docgen-extract-anchors.mjs +48 -13
  179. package/skills/{docgen → doc-files}/js/docgen-extract.mjs +39 -10
  180. package/skills/doc-files/js/docgen-files-batch.mjs +181 -0
  181. package/skills/doc-files/js/docgen-gen.mjs +336 -0
  182. package/skills/{docgen → doc-files}/js/docgen-prompts.mjs +65 -50
  183. package/skills/doc-files/js/docgen-scan.mjs +298 -0
  184. package/skills/doc-files/js/docs/docgen-crc.md +32 -0
  185. package/skills/doc-files/js/docs/docgen-extract-anchors.md +27 -0
  186. package/skills/doc-files/js/docs/docgen-extract.md +29 -0
  187. package/skills/doc-files/js/docs/docgen-files-batch.md +25 -0
  188. package/skills/doc-files/js/docs/docgen-gen.md +30 -0
  189. package/skills/doc-files/js/docs/docgen-prompts.md +32 -0
  190. package/skills/doc-files/js/docs/docgen-scan.md +25 -0
  191. package/skills/doc-files/js/units-js.mjs +139 -0
  192. package/skills/doc-files/js/units.mjs +19 -0
  193. package/skills/doc-files/meta.json +1 -0
  194. package/skills/fix/js/docs/llm-worker.md +6 -0
  195. package/skills/fix/js/docs/orchestrator.md +6 -0
  196. package/skills/fix/js/llm-worker.mjs +3 -3
  197. package/skills/fix/js/orchestrator.mjs +1 -1
  198. package/skills/start-check/js/check.mjs +5 -3
  199. package/skills/start-check/js/docs/check.md +6 -0
  200. package/skills/docgen/SKILL.md +0 -224
  201. package/skills/docgen/bench/etalon/firebase_hosting.md +0 -19
  202. package/skills/docgen/bench/etalon/k8s-tree.md +0 -24
  203. package/skills/docgen/bench/etalon/overlay-paths.md +0 -24
  204. package/skills/docgen/js/docgen-batch-omlx.mjs +0 -82
  205. package/skills/docgen/js/docgen-batch.mjs +0 -95
  206. package/skills/docgen/js/docgen-compare-pi-vs-direct.mjs +0 -95
  207. package/skills/docgen/js/docgen-gen.mjs +0 -306
  208. package/skills/docgen/js/docs/docgen-extract.md +0 -28
  209. package/skills/docgen/js/docs/docgen-gen.md +0 -41
  210. package/skills/docgen/js/docs/docgen-ignore.md +0 -24
  211. package/skills/docgen/js/docs/docgen-prompts.md +0 -24
  212. package/skills/docgen/js/docs/docgen-scan.md +0 -48
  213. /package/skills/{docgen → doc-aggregate}/meta.json +0 -0
  214. /package/skills/{docgen → doc-files}/js/docgen-ignore.mjs +0 -0
@@ -20,6 +20,28 @@
20
20
  "timeout": 300
21
21
  }
22
22
  ]
23
+ },
24
+ {
25
+ "matcher": "Edit|Write|MultiEdit",
26
+ "hooks": [
27
+ {
28
+ "type": "command",
29
+ "command": "npx --no @nitra/cursor doc-files check --hook",
30
+ "timeout": 120
31
+ }
32
+ ]
33
+ }
34
+ ],
35
+ "Stop": [
36
+ {
37
+ "matcher": "",
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "npx --no @nitra/cursor doc-files check --git",
42
+ "timeout": 120
43
+ }
44
+ ]
23
45
  }
24
46
  ]
25
47
  }
@@ -1,3 +1,9 @@
1
+ ---
2
+ docgen:
3
+ source: npm/.pi-template/extensions/n-cursor-adr/index.ts
4
+ crc: 3233716f
5
+ ---
6
+
1
7
  # index.ts
2
8
 
3
9
  ## Огляд
@@ -23,12 +29,12 @@
23
29
 
24
30
  ## Гарантії поведінки
25
31
 
26
- * Доступ до файлу дозволений
27
- * Операція запису та модифікації даних дозволена
28
- * При виникненні помилок система перехоплює їх
29
- * Система не генерує винятків назовні
30
- * Система не використовує кешування
31
- * Система не виконує операцій з мережею
32
- * Логіка пропуску та обмеження швидкості залишається у бах (bash)
33
- * Логіка вибору LLM-CLI залишається у бах (bash)
34
- * Перевірка рекурсії здійснюється через змінні середовища встановлені бах перед запуском LLM CLI
32
+ - Доступ до файлу дозволений
33
+ - Операція запису та модифікації даних дозволена
34
+ - При виникненні помилок система перехоплює їх
35
+ - Система не генерує винятків назовні
36
+ - Система не використовує кешування
37
+ - Система не виконує операцій з мережею
38
+ - Логіка пропуску та обмеження швидкості залишається у бах (bash)
39
+ - Логіка вибору LLM-CLI залишається у бах (bash)
40
+ - Перевірка рекурсії здійснюється через змінні середовища встановлені бах перед запуском LLM CLI
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.2.1] - 2026-06-11
4
+
5
+ ### Changed
6
+
7
+ - adr
8
+
9
+ ## [5.2.0] - 2026-06-11
10
+
11
+ ### Changed
12
+
13
+ - doc-files: local-only конвеєр (Крок 0 за ADR 260610-2228) — спільний npm/lib/llm.mjs з роутингом за префіксом omlx/ і wire-trace; видалено cloud-ескалації та pre-route sym; degraded-маркер score/issues у frontmatter, gen --retry-degraded і check --degraded; preflight omlx (down/memory-guard/auth) перед масовим прогоном; callOmlx шле Authorization Bearer (N_CURSOR_OMLX_KEY або ~/.omlx/settings.json)
14
+ - doc-files: міграція корпусу док на CRC-контракт — 126 git-актуальним докам застамповано frontmatter (дока не старіша за джерело за git-історією); 76 файлів (73 git-відсталі доки + 3 без док) лишено stale як чесний беклог перегенерації локальним конвеєром
15
+
16
+ ### Fixed
17
+
18
+ - lint: санація шарів, що ховалися за першим падінням oxlint — повний bun run lint доведено до lint-js/style зелених: JSDoc/regex/sonarjs фікси у ~25 файлах, slow-regex обмежено bounded-квантифікаторами, eslint ignore для згенерованих docs/*.md, jscpd ignore (.git, COVERAGE.md, docs, відомі легасі-клони), knip ignoreBinaries (pi/claude/fix-t0), stylelint/cspell ignore згенерованих артефактів; docgen-ignore у doc-aggregate став re-export з doc-files
19
+
3
20
  ## [5.1.0] - 2026-06-10
4
21
 
5
22
  ### Changed
@@ -75,7 +92,7 @@
75
92
 
76
93
  ### Added
77
94
 
78
- - lib/models.mjs: global model tier classification (LOCAL_MIN/AVG/MAX, CLOUD_MIN/AVG/MAX) via N_*_MODEL env vars; fix llm-worker uses CLOUD_MIN/AVG by default
95
+ - lib/models.mjs: global model tier classification (LOCAL*MIN/AVG/MAX, CLOUD_MIN/AVG/MAX) via N*\*\_MODEL env vars; fix llm-worker uses CLOUD_MIN/AVG by default
79
96
 
80
97
  ### Changed
81
98
 
package/bin/n-cursor.js CHANGED
@@ -25,8 +25,11 @@
25
25
  * `npx \@nitra/cursor lint-docker` — канонічний lint-docker (docker.mdc): `hadolint` по `Dockerfile`/`*.Dockerfile`
26
26
  * `npx \@nitra/cursor lint-text` — канонічний lint-text (text.mdc): `cspell` → `shellcheck` (з auto-fix) →
27
27
  * `markdownlint-cli2 --fix` → `v8r` (json/json5/yaml/yml/toml)
28
- * `npx \@nitra/cursor docgen scan` детермінований JSON-лістинг кодових файлів для скілу docgen (відносні `sourcePath`; ignore-glob snippet у `npm/skills/docgen/js/docgen-ignore.mjs`; тека `docs/` поряд із джерелом)
29
- * `npx \@nitra/cursor docgen modules` — детермінований JSON-лістинг логічних модулів (межі за `package.json`) для Tier 2 скілу docgen
28
+ * `npx \@nitra/cursor doc-files scan` — JSON-лістинг кодових файлів зі станом застарілості (`stale`: `missing`|`crc-mismatch`) для обовʼязкового скілу doc-files (ignore-glob у `npm/skills/doc-files/js/docgen-ignore.mjs`; тека `docs/` поряд із джерелом)
29
+ * `npx \@nitra/cursor doc-files check` — детектор застарілості для hook'ів (`--hook` PostToolUse, `--git` Stop-гейт із порогом `--max`); exit 2 стале знайдено
30
+ * `npx \@nitra/cursor doc-files gen` — JS-оркестрована генерація файлових док (роутинг local/cloud) зі штампом CRC у frontmatter (`--limit`/`--from`/`--overwrite`)
31
+ * `npx \@nitra/cursor doc-files stamp` — детерміновано (пере)штампувати CRC у наявних доках без LLM
32
+ * `npx \@nitra/cursor doc-aggregate modules` — JSON-лістинг логічних модулів (межі за `package.json`) для Tier 2 скілу doc-aggregate
30
33
  * `npx \@nitra/cursor skill list` — скіли пакета без синку в проєкт
31
34
  * `npx \@nitra/cursor skill taze` — промпт на stdout
32
35
  * `npx \@nitra/cursor skill cursor taze ["task"]` — Cursor CLI (`cursor-agent -p`)
@@ -663,6 +666,22 @@ function buildClaudeWorktreeEnforcementSectionLines() {
663
666
  ]
664
667
  }
665
668
 
669
+ /**
670
+ * Рендерить секцію для CLAUDE.md: doc-files — обовʼязковий крок задачі (як lint).
671
+ * Після зміни кодового файлу його дока має бути перегенерована; застарілість
672
+ * детермінується за CRC і контролюється Stop-hook'ом.
673
+ * @returns {string[]} рядки для вставки (з порожнім рядком на початку)
674
+ */
675
+ function buildClaudeDocFilesSectionLines() {
676
+ return [
677
+ '',
678
+ '## Файлова документація (`doc-files` — обовʼязковий крок, як lint)',
679
+ '',
680
+ 'Після зміни чи додавання кодового файлу його файлова дока (`<dir>/docs/<stem>.md`) має бути **актуальною** — це **обовʼязковий крок кожної задачі**, нарівні з lint. Застарілість детермінується за **CRC** джерела у frontmatter доки. PostToolUse hook (`doc-files check --hook`) **сигналить** про дрейф після правки; Stop-hook (`doc-files check --git`) **блокує завершення** задачі за наявності застарілих док (виняток — масовий прогін понад поріг `N_CURSOR_DOC_FILES_GATE_MAX`, дефолт 50). Регенерація — `/doc-files` (JS-оркестрована, не диспатч субагентів). Агрегуюча дока (module-summary, доменні) — окремий скіл `/doc-aggregate`, за запитом.',
681
+ ''
682
+ ]
683
+ }
684
+
666
685
  /**
667
686
  * Рендерить секцію Skills для CLAUDE.md з урахуванням наявних slash-команд.
668
687
  * @returns {Promise<string[]>} готові рядки секції (або порожній масив)
@@ -722,7 +741,11 @@ async function syncClaudeMd(ignore) {
722
741
  lines.push(`@${RULES_DIR}/${mdcFile}`)
723
742
  }
724
743
 
725
- lines.push(...buildClaudeLintParallelismSectionLines(), ...buildClaudeWorktreeEnforcementSectionLines())
744
+ lines.push(
745
+ ...buildClaudeLintParallelismSectionLines(),
746
+ ...buildClaudeWorktreeEnforcementSectionLines(),
747
+ ...buildClaudeDocFilesSectionLines()
748
+ )
726
749
 
727
750
  const skillsSectionLines = await buildClaudeSkillsSectionLines()
728
751
  lines.push(...skillsSectionLines)
@@ -1550,7 +1573,7 @@ async function runSync() {
1550
1573
  /**
1551
1574
  * Команди, що мутують проєкт у CWD і вимагають кореня репо. `undefined`/`''` —
1552
1575
  * дефолтний sync; `check` — deprecated-alias `fix`. Решта (read-only `trace`,
1553
- * `--root`-команди `docgen`/`rename-yaml-extensions`, `worktree`,
1576
+ * `--root`-команди `doc-files`/`doc-aggregate`/`rename-yaml-extensions`, `worktree`,
1554
1577
  * sub-лінтери) гард не зачіпає.
1555
1578
  */
1556
1579
  const ROOT_GUARDED_COMMANDS = new Set([undefined, '', 'fix', 'check', 'lint', 'coverage', 'change', 'release'])
@@ -1596,7 +1619,7 @@ try {
1596
1619
  // .n-cursor.json + bun install, а fix/lint/coverage/change/release переписують файли в CWD —
1597
1620
  // усе це ключиться на cwd(). Запуск із піддиректорії git-репо (типово прямий
1598
1621
  // `bun npm/bin/n-cursor.js` не з кореня) зачепив би не той каталог → STOP. Read-only та
1599
- // `--root`-команди (trace, graph, docgen, rename-yaml-extensions) не зачіпаємо.
1622
+ // `--root`-команди (trace, graph, doc-files, doc-aggregate, rename-yaml-extensions) не зачіпаємо.
1600
1623
  if (ROOT_GUARDED_COMMANDS.has(command)) {
1601
1624
  assertCwdIsProjectRoot(cwd(), describeRootGuardedAction(command))
1602
1625
  }
@@ -1765,17 +1788,51 @@ try {
1765
1788
 
1766
1789
  break
1767
1790
  }
1768
- case 'docgen': {
1769
- // n-cursor docgen scan|modulesдетермінований лістинг для скілу docgen.
1770
- // scan кодові файли; modules логічні модулі (межі за package.json).
1771
- // Друкує JSON; генерацію доки робить скіл, диспатчачи субагентів.
1772
- const { runDocgenScanCli, runDocgenModulesCli } = await import('../skills/docgen/js/docgen-scan.mjs')
1773
- if (args[0] === 'scan') {
1774
- process.exitCode = await runDocgenScanCli(args.slice(1))
1775
- } else if (args[0] === 'modules') {
1776
- process.exitCode = await runDocgenModulesCli(args.slice(1))
1791
+ case 'doc-files': {
1792
+ // n-cursor doc-files scan|check|gen|stampобовʼязковий крок задачі (як lint):
1793
+ // файлова дока поряд із джерелом, застарілість за CRC у frontmatter.
1794
+ // scan — JSON-лістинг кодових файлів зі станом stale (missing|crc-mismatch);
1795
+ // check детектор застарілості для hook'ів (--hook PostToolUse, --git Stop-гейт);
1796
+ // gen — JS-оркестрована генерація доки (роутинг local/cloud) + CRC-штамп;
1797
+ // stamp детерміновано (пере)штампувати CRC у наявних доках без LLM.
1798
+ switch (args[0]) {
1799
+ case 'scan': {
1800
+ const { runDocFilesScanCli } = await import('../skills/doc-files/js/docgen-scan.mjs')
1801
+ process.exitCode = runDocFilesScanCli(args.slice(1))
1802
+ break
1803
+ }
1804
+ case 'check': {
1805
+ const { runDocFilesCheckCli } = await import('../skills/doc-files/js/docgen-scan.mjs')
1806
+ process.exitCode = await runDocFilesCheckCli(args.slice(1))
1807
+ break
1808
+ }
1809
+ case 'gen': {
1810
+ const { runDocFilesGenCli } = await import('../skills/doc-files/js/docgen-files-batch.mjs')
1811
+ process.exitCode = await runDocFilesGenCli(args.slice(1))
1812
+ break
1813
+ }
1814
+ case 'stamp': {
1815
+ const { runDocFilesStampCli } = await import('../skills/doc-files/js/docgen-files-batch.mjs')
1816
+ process.exitCode = runDocFilesStampCli(args.slice(1))
1817
+ break
1818
+ }
1819
+ default: {
1820
+ console.error('Usage: npx @nitra/cursor doc-files <scan|check|gen|stamp> [--root <dir>]')
1821
+ process.exitCode = 1
1822
+ }
1823
+ }
1824
+
1825
+ break
1826
+ }
1827
+ case 'doc-aggregate': {
1828
+ // n-cursor doc-aggregate modules — детермінований лістинг логічних модулів
1829
+ // (межі за package.json) для Tier 2 module-summary скілу doc-aggregate.
1830
+ // Друкує JSON; module-summary і доменні доки пише скіл, диспатчачи субагентів.
1831
+ const { runDocAggregateModulesCli } = await import('../skills/doc-aggregate/js/docgen-scan.mjs')
1832
+ if (args[0] === 'modules') {
1833
+ process.exitCode = await runDocAggregateModulesCli(args.slice(1))
1777
1834
  } else {
1778
- console.error('Usage: npx @nitra/cursor docgen <scan|modules> [--root <dir>]')
1835
+ console.error('Usage: npx @nitra/cursor doc-aggregate <modules> [--root <dir>]')
1779
1836
  process.exitCode = 1
1780
1837
  }
1781
1838
 
@@ -1790,7 +1847,7 @@ try {
1790
1847
  default: {
1791
1848
  console.error(`❌ Невідома команда: ${command}`)
1792
1849
  console.error(
1793
- ` Очікується: (без аргументів) синхронізація правил, fix, check, rename-yaml-extensions, post-tool-use-fix, lint, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, coverage, coverage-fix, taze, start-check, fix-t0, change, release, skill, worktree, lint-ci, trace, docgen`
1850
+ ` Очікується: (без аргументів) синхронізація правил, fix, check, rename-yaml-extensions, post-tool-use-fix, lint, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, coverage, coverage-fix, taze, start-check, fix-t0, change, release, skill, worktree, lint-ci, trace, doc-files, doc-aggregate`
1794
1851
  )
1795
1852
  process.exitCode = 1
1796
1853
  }
@@ -1,3 +1,9 @@
1
+ ---
2
+ docgen:
3
+ source: npm/stryker.config.mjs
4
+ crc: b8c7377b
5
+ ---
6
+
1
7
  # stryker.config.mjs
2
8
 
3
9
  ## Огляд
@@ -1,3 +1,9 @@
1
+ ---
2
+ docgen:
3
+ source: npm/vitest.config.js
4
+ crc: d4917e1f
5
+ ---
6
+
1
7
  # vitest.config.js
2
8
 
3
9
  ## Огляд
@@ -0,0 +1,29 @@
1
+ ---
2
+ docgen:
3
+ source: npm/lib/llm.mjs
4
+ crc: 9ef9f20f
5
+ ---
6
+
7
+ # llm
8
+
9
+ ## Огляд
10
+
11
+ Єдина точка LLM-викликів для JS-оркестраторів монорепо: ховає вибір транспорту за конвенцією префікса model-id і дає preflight-перевірку локального сервера перед масовими прогонами.
12
+
13
+ ## Поведінка
14
+
15
+ 1. Виклик із model-id з префіксом `omlx/` іде прямим HTTP до локального omlx-сервера зі збереженням ролей повідомлень; будь-який інший model-id (зокрема порожній) — через `pi` CLI, де повідомлення конкатенуються в один текстовий промпт, а інструменти вимкнено.
16
+ 2. Якщо виставлено `N_CURSOR_LLM_TRACE` зі шляхом до файлу, кожен виклик дописує один JSONL-рядок: бекенд, модель, тривалість, розміри промпта/відповіді, успіх або текст помилки. Невдалий запис трейсу не впливає на сам виклик.
17
+ 3. Preflight локального сервера робить мінімальний запит (одна позиція виходу) і класифікує стан: сервер лежить (`down`), модель не влазить у динамічну стелю пам'яті зайнятої машини (`memory-guard`), сервер вимагає API-ключ (`auth`), інша помилка (`error`). Порожня відповідь живого сервера вважається здоровим станом.
18
+
19
+ ## Публічний API
20
+
21
+ - `callLlm` — універсальний виклик за messages-масивом із маршрутизацією за префіксом model-id.
22
+ - `pickBackend` — назва бекенда (`omlx` чи `pi`) для заданого model-id.
23
+ - `omlxHealthCheck` — preflight-стан локального сервера: `{ ok, reason, detail }`.
24
+
25
+ ## Гарантії поведінки
26
+
27
+ - Маршрутизація залежить лише від рядка моделі — жодних env-перемикачів бекенда.
28
+ - Помилка транспорту прокидається винятком; трейс при цьому фіксує невдачу.
29
+ - `memory-guard` означає «машина зайнята, спробуй пізніше», а не дефект моделі чи конвеєра.
@@ -1,30 +1,37 @@
1
+ ---
2
+ docgen:
3
+ source: npm/lib/models.mjs
4
+ crc: feb82992
5
+ score: 100
6
+ ---
7
+
1
8
  # models.mjs
2
9
 
3
10
  ## Огляд
4
11
 
5
- Файл визначає ієрархічну класифікацію моделей для системи pi. Класифікація встановлює зв'язок між локальними та хмарними провайдерами. Функція resolveModel забезпечує маршрутизацію вибору моделі залежно від заданого рівня доступності.
12
+ Файл визначає глобальну класифікацію моделей для системи pi, встановлюючи конфігураційні моделі для локального та хмарного інференсу через змінні середовища (наприклад, `N_LOCAL_MIN_MODEL`). Значення моделі мають формат "provider/model-id".
13
+
14
+ Система надає механізм каскадного вибору моделі через функцію `resolveModel`. Цей механізм послідовно перевіряє локальні тири (`LOCAL_MIN` $\rightarrow$ `LOCAL_AVG` $\rightarrow$ `LOCAL_MAX`), а потім хмарні тири, якщо попередні не визначені. Це забезпечує прозору роботу, навіть якщо локальні моделі відсутні. Прямі константи (наприклад, `LOCAL_MIN`) залишені для випадків, що вимагають явного контролю над вибором моделі.
6
15
 
7
16
  ## Поведінка
8
17
 
9
- LOCAL_MIN встановлює мінімальний локальний провайдер
10
- LOCAL_AVG встановлює середній локальний провайдер
11
- LOCAL_MAX встановлює максимальний локальний провайдер
12
- CLOUD_MIN встановлює мінімальний хмарний провайдер
13
- CLOUD_AVG встановлює середній хмарний провайдер
14
- CLOUD_MAX встановлює максимальний хмарний провайдер
15
- resolveModel повертає перший непорожній model-id з каскадного перевірки локальних та хмарних провайдерів
16
- resolveModel приймає тир min avg або max
17
- resolveModel повертає model-id або порожній рядок якщо жоден тир не задано
18
+ LOCAL_MIN повертає модель для швидкого локального інференсу, або порожній рядок, якщо змінна середовища не встановлена.
19
+ LOCAL_AVG повертає модель для середнього локального інференсу, або порожній рядок, якщо змінна середовища не встановлена.
20
+ LOCAL_MAX повертає модель для максимального локального інференсу, або порожній рядок, якщо змінна середовища не встановлена.
21
+ CLOUD_MIN повертає модель для мінімального хмарного інференсу, або порожній рядок, якщо змінна середовища не встановлена.
22
+ CLOUD_AVG повертає модель для середнього хмарного інференсу, або порожній рядок, якщо змінна середовища не встановлена.
23
+ CLOUD_MAX повертає модель для максимального хмарного інференсу, або порожній рядок, якщо змінна середовища не встановлена.
24
+ resolveModel повертає перший непорожній model-id для запитаного тиру, каскадно перевіряючи локальні тири, а потім хмарний еквівалент, або порожній рядок, якщо жоден тир не задано.
18
25
 
19
26
  ## Публічний API
20
27
 
21
- LOCAL_MIN — Виконує швидкий локальний inference.
22
- LOCAL_AVG — Виконує середній локальний inference.
23
- LOCAL_MAX — Виконує максимальний локальний inference.
24
- CLOUD_MIN — Виконує мінімальний хмарний inference.
25
- CLOUD_AVG — Виконує середній хмарний inference.
26
- CLOUD_MAX — Виконує максимальний хмарний inference.
27
- resolveModel — Повертає перший непорожній model-id для запиту, перевіряючи спочатку локальні, а потім хмарні варіанти.
28
+ LOCAL_MIN — Швидке виконання моделі на локальному пристрої.
29
+ LOCAL_AVG — Середнє за продуктивністю виконання моделі на локальному пристрої.
30
+ LOCAL_MAX — Найпотужніше виконання моделі на локальному пристрої.
31
+ CLOUD_MIN — Найменш ресурсомістке виконання моделі в хмарі.
32
+ CLOUD_AVG — Середній рівень продуктивності виконання моделі в хмарі.
33
+ CLOUD_MAX — Найпотужніше виконання моделі в хмарі.
34
+ resolveModel — Знаходить і повертає ідентифікатор моделі, починаючи з локальних варіантів, а потім переходячи до хмарних відповідників.
28
35
 
29
36
  ## Гарантії поведінки
30
37
 
@@ -0,0 +1,32 @@
1
+ ---
2
+ docgen:
3
+ source: npm/lib/omlx.mjs
4
+ crc: e29efc97
5
+ ---
6
+
7
+ # omlx
8
+
9
+ ## Огляд
10
+
11
+ Спільний транспорт до локального omlx-сервера (OpenAI-сумісний MLX-inference на `http://127.0.0.1:8000/v1/chat/completions`). Text-only: сервер не підтримує інструменти, тож модуль шле лише повідомлення й читає текст відповіді.
12
+
13
+ ## Поведінка
14
+
15
+ 1. Виклик будує запит з повідомленнями, моделлю, температурою і лімітом виходу; model-id з префіксом `omlx/` нормалізується до чистого імені, порожній — замінюється дефолтною моделлю (`mlx-community--gemma-4-e2b-it-4bit`, override через `N_CURSOR_OMLX_MODEL`).
16
+ 2. Endpoint береться з `N_CURSOR_OMLX_URL` або дефолтний.
17
+ 3. Якщо на сервері ввімкнено API-ключ, він резолвиться в порядку: явний параметр виклику → `N_CURSOR_OMLX_KEY` → поле `auth.api_key` з локального `~/.omlx/settings.json` (читання fail-safe) — і шлеться як `Authorization: Bearer`. Без знайденого ключа заголовок не додається.
18
+ 4. Тимчасові обриви з'єднання (передача перервана, порожня відповідь, збій прийому) ретраяться до трьох спроб; решта помилок — виняток одразу.
19
+ 5. Помилка API у тілі відповіді, невалідний JSON чи порожній контент — виняток із діагностичним префіксом `omlx …`.
20
+
21
+ ## Публічний API
22
+
23
+ - `callOmlx` — синхронний chat-виклик, повертає непорожній текст відповіді.
24
+ - `isOmlxModel` / `omlxModelId` — перевірка і зняття префікса `omlx/`.
25
+ - `resolveOmlxApiKey` — резолвінг API-ключа за описаним порядком джерел.
26
+ - `DEFAULT_OMLX_URL` / `DEFAULT_OMLX_MODEL` — дефолти endpoint і моделі.
27
+
28
+ ## Гарантії поведінки
29
+
30
+ - Жодних `tools`/`tool_calls` у запиті — сумісність із text-only сервером.
31
+ - Ролі повідомлень (`system`/`user`) передаються без злиття.
32
+ - Виняток ніколи не ковтається: викликач завжди дізнається про збій.
package/lib/llm.mjs ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Єдина точка LLM-викликів для JS-оркестраторів (див. ADR 260610-2228).
3
+ *
4
+ * Маршрутизація — виключно за префіксом model-id (конвенція `npm/lib/models.mjs`):
5
+ * `omlx/<model>` → прямий HTTP до локального omlx-сервера (`callOmlx`)
6
+ * будь-що інше → `pi` CLI (хмарні провайдери або pi-дефолт)
7
+ *
8
+ * Жодних env-перемикачів бекенда: рядок моделі сам визначає транспорт.
9
+ *
10
+ * Wire-trace (ADR 260610-1516/1524): якщо виставлено `N_CURSOR_LLM_TRACE=<file>`,
11
+ * кожен виклик append-ить один JSONL-рядок з бекендом, моделлю, тривалістю і
12
+ * розмірами prompt/output. Трейс fail-safe: помилка запису не ламає виклик.
13
+ */
14
+ import { spawnSync } from 'node:child_process'
15
+ import { appendFileSync } from 'node:fs'
16
+ import { env } from 'node:process'
17
+
18
+ import { callOmlx, isOmlxModel } from './omlx.mjs'
19
+
20
+ /** Дефолтний timeout одного виклику (узгоджено з LOCAL_TIMEOUT доки-конвеєра). */
21
+ const DEFAULT_TIMEOUT_MS = 120_000
22
+
23
+ /**
24
+ * Бекенд для model-id: `omlx` — прямий HTTP, `pi` — CLI.
25
+ * @param {string} model model-id (можливо порожній — pi-дефолт)
26
+ * @returns {'omlx'|'pi'} назва бекенда
27
+ */
28
+ export function pickBackend(model) {
29
+ return isOmlxModel(model) ? 'omlx' : 'pi'
30
+ }
31
+
32
+ /**
33
+ * Fail-safe append JSONL-рядка трейсу у файл з `N_CURSOR_LLM_TRACE`.
34
+ * @param {object} entry один запис трейсу
35
+ */
36
+ function trace(entry) {
37
+ const file = env.N_CURSOR_LLM_TRACE
38
+ if (!file) return
39
+ try {
40
+ appendFileSync(file, JSON.stringify(entry) + '\n')
41
+ } catch {
42
+ // трейс не має ламати основний виклик
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Виклик через `pi` CLI: messages конкатенуються у plain prompt
48
+ * (pi не приймає messages-масив), tools вимкнено.
49
+ * @param {Array<{role:string, content:string}>} messages OpenAI-style messages
50
+ * @param {string} model model-id для `--model` (порожній — pi-дефолт)
51
+ * @param {number} timeoutMs ліміт очікування процесу
52
+ * @returns {string} stdout відповіді
53
+ */
54
+ function callPi(messages, model, timeoutMs) {
55
+ const prompt = messages.map(m => m.content).join('\n\n')
56
+ const modelArgs = model ? ['--model', model] : []
57
+ const r = spawnSync('pi', ['-p', prompt, ...modelArgs, '--no-session', '--mode', 'text', '--no-tools'], {
58
+ encoding: 'utf8',
59
+ timeout: timeoutMs
60
+ })
61
+ if (r.error) throw new Error(`pi error: ${r.error.message}`)
62
+ if (r.status !== 0) throw new Error(`pi exit ${r.status}: ${r.stderr?.slice(0, 300) ?? ''}`)
63
+ return r.stdout?.trim() ?? ''
64
+ }
65
+
66
+ /**
67
+ * Універсальний LLM-виклик з маршрутизацією за префіксом model-id.
68
+ * @param {Array<{role:string, content:string}>} messages OpenAI-style messages (system зберігається на omlx)
69
+ * @param {string} model model-id; `omlx/<m>` → прямий HTTP, інакше → pi CLI
70
+ * @param {{ timeoutMs?: number, temperature?: number, maxTokens?: number, url?: string }} [opts] timeout, температура, ліміт виходу, override URL
71
+ * @returns {string} текст відповіді (непорожній на omlx; pi може повернути '')
72
+ */
73
+ export function callLlm(messages, model, opts = {}) {
74
+ const { timeoutMs = DEFAULT_TIMEOUT_MS, temperature = 0.2, maxTokens, url } = opts
75
+ const backend = pickBackend(model)
76
+ const t0 = Date.now()
77
+ const promptChars = messages.reduce((n, m) => n + (m.content?.length ?? 0), 0)
78
+ try {
79
+ const out =
80
+ backend === 'omlx'
81
+ ? callOmlx(messages, model, { url, timeoutMs, temperature, ...(maxTokens ? { maxTokens } : {}) })
82
+ : callPi(messages, model, timeoutMs)
83
+ trace({
84
+ ts: new Date().toISOString(),
85
+ backend,
86
+ model,
87
+ ms: Date.now() - t0,
88
+ promptChars,
89
+ outChars: out.length,
90
+ ok: true
91
+ })
92
+ return out
93
+ } catch (error) {
94
+ trace({
95
+ ts: new Date().toISOString(),
96
+ backend,
97
+ model,
98
+ ms: Date.now() - t0,
99
+ promptChars,
100
+ ok: false,
101
+ error: String(error.message).slice(0, 200)
102
+ })
103
+ throw error
104
+ }
105
+ }
106
+
107
+ /** Фрагмент повідомлення omlx про memory-guard (динамічна стеля пам'яті). */
108
+ const MEMORY_GUARD_MARKER = 'memory ceiling'
109
+ /** Тип помилки omlx про відсутній/хибний API-ключ. */
110
+ const AUTH_ERROR_MARKER = 'authentication_error'
111
+
112
+ /**
113
+ * Preflight-перевірка omlx перед масовим прогоном: мінімальний chat-виклик
114
+ * (`max_tokens: 1`). Розрізняє стани, які вимагають різних дій:
115
+ * - `down` — сервер не відповідає (не запущений / не той порт);
116
+ * - `memory-guard` — модель не влазить у динамічну стелю пам'яті зайнятої
117
+ * машини → «відклади прогін», а не «модель погана»;
118
+ * - `auth` — сервер вимагає API-ключ → вистав `N_CURSOR_OMLX_KEY`;
119
+ * - `error` — інша помилка API.
120
+ * Порожній контент відповіді — це ok: сервер живий і модель завантажена.
121
+ * @param {{ url?: string, model?: string, timeoutMs?: number }} [opts] override URL/моделі/timeout перевірки
122
+ * @returns {{ ok: boolean, reason: 'down'|'memory-guard'|'auth'|'error'|null, detail: string }} стан сервера і класифікована причина збою
123
+ */
124
+ export function omlxHealthCheck(opts = {}) {
125
+ const { url, model = '', timeoutMs = DEFAULT_TIMEOUT_MS } = opts
126
+ try {
127
+ callOmlx([{ role: 'user', content: 'ok' }], model, { url, timeoutMs, maxTokens: 1, temperature: 0 })
128
+ return { ok: true, reason: null, detail: '' }
129
+ } catch (error) {
130
+ const detail = String(error.message)
131
+ if (detail.includes(MEMORY_GUARD_MARKER)) return { ok: false, reason: 'memory-guard', detail }
132
+ if (detail.includes(AUTH_ERROR_MARKER)) return { ok: false, reason: 'auth', detail }
133
+ if (detail.startsWith('omlx empty content')) return { ok: true, reason: null, detail }
134
+ if (detail.startsWith('omlx curl')) return { ok: false, reason: 'down', detail }
135
+ return { ok: false, reason: 'error', detail }
136
+ }
137
+ }
package/lib/omlx.mjs CHANGED
@@ -11,13 +11,40 @@
11
11
  * Так `resolveModel(tier)` лишається незмінним: достатньо виставити локальний
12
12
  * тир у форматі `N_LOCAL_MIN_MODEL=omlx/mlx-community--gemma-4-e2b-it-4bit`, і
13
13
  * виклик сам піде напряму в omlx замість pi.
14
+ *
15
+ * Auth: якщо в omlx увімкнено API-ключ, він резолвиться через
16
+ * `resolveOmlxApiKey` (opts → `N_CURSOR_OMLX_KEY` → `~/.omlx/settings.json`)
17
+ * і шлеться як `Authorization: Bearer …`.
14
18
  */
15
19
  import { spawnSync } from 'node:child_process'
20
+ import { readFileSync } from 'node:fs'
21
+ import { homedir } from 'node:os'
22
+ import { join } from 'node:path'
16
23
  import { env } from 'node:process'
17
24
 
18
25
  /** Дефолтний endpoint omlx (override — `N_CURSOR_OMLX_URL`). */
19
26
  export const DEFAULT_OMLX_URL = 'http://127.0.0.1:8000/v1/chat/completions'
20
27
 
28
+ /**
29
+ * API-ключ для omlx-сервера, коли в ньому ввімкнено auth
30
+ * (`~/.omlx/settings.json` → `auth.skip_api_key_verification: false`).
31
+ * Порядок: явний `apiKey` → env `N_CURSOR_OMLX_KEY` → `auth.api_key` із
32
+ * локального `~/.omlx/settings.json` (zero-config для власної машини; читання
33
+ * fail-safe) → `null` (заголовок не шлеться).
34
+ * @param {string} [apiKey] явний ключ із opts виклику
35
+ * @returns {string|null} ключ для `Authorization: Bearer …` або null
36
+ */
37
+ export function resolveOmlxApiKey(apiKey) {
38
+ if (apiKey) return apiKey
39
+ if (env.N_CURSOR_OMLX_KEY) return env.N_CURSOR_OMLX_KEY
40
+ try {
41
+ const settings = JSON.parse(readFileSync(join(homedir(), '.omlx', 'settings.json'), 'utf8'))
42
+ return settings?.auth?.api_key || null
43
+ } catch {
44
+ return null
45
+ }
46
+ }
47
+
21
48
  /** Дефолтна модель, якщо в id лишився голий `omlx/` (override — `N_CURSOR_OMLX_MODEL`). */
22
49
  export const DEFAULT_OMLX_MODEL = 'mlx-community--gemma-4-e2b-it-4bit'
23
50
 
@@ -46,10 +73,9 @@ export function omlxModelId(model) {
46
73
  * Прямий HTTP-виклик до omlx через `curl` (spawnSync). Повертає текст
47
74
  * `choices[0].message.content`. Ретраїть лише transient curl-помилки
48
75
  * (18 = transfer closed, 52 = empty reply, 56 = recv failure).
49
- *
50
76
  * @param {Array<{role:string, content:string}>} messages OpenAI-messages (system+user збережено)
51
77
  * @param {string} model model-id (з/без `omlx/`-префікса); порожній → дефолт
52
- * @param {{ url?: string, timeoutMs?: number, temperature?: number, maxTokens?: number, fallbackModel?: string }} [opts]
78
+ * @param {{ url?: string, timeoutMs?: number, temperature?: number, maxTokens?: number, fallbackModel?: string, apiKey?: string }} [opts] URL, timeout, температура, ліміт виходу, fallback-модель, API-ключ
53
79
  * @returns {string} непорожній контент відповіді
54
80
  * @throws на curl-помилці, не-200 exit, поганому JSON чи порожньому контенті
55
81
  */
@@ -59,18 +85,37 @@ export function callOmlx(messages, model, opts = {}) {
59
85
  timeoutMs = 60_000,
60
86
  temperature = 0.2,
61
87
  maxTokens = 4096,
62
- fallbackModel = env.N_CURSOR_OMLX_MODEL ?? DEFAULT_OMLX_MODEL
88
+ fallbackModel = env.N_CURSOR_OMLX_MODEL ?? DEFAULT_OMLX_MODEL,
89
+ apiKey
63
90
  } = opts
64
91
 
65
92
  const m = omlxModelId(model) || fallbackModel
66
93
  const body = JSON.stringify({ model: m, messages, max_tokens: maxTokens, temperature })
94
+ // Ключ локального сервера в argv допустимий: localhost-секрет власної машини,
95
+ // короткоживучий процес; stdin уже зайнятий body (`--data-binary @-`).
96
+ const key = resolveOmlxApiKey(apiKey)
97
+ const authArgs = key ? ['-H', `Authorization: Bearer ${key}`] : []
67
98
 
68
99
  const TRANSIENT_CURL_CODES = new Set([18, 52, 56])
69
100
  let lastErr
70
101
  for (let attempt = 1; attempt <= 3; attempt++) {
71
102
  const r = spawnSync(
72
103
  'curl',
73
- ['-sS', '-X', 'POST', url, '-H', 'Content-Type: application/json', '-H', 'Connection: close', '--max-time', String(Math.ceil(timeoutMs / 1000)), '--data-binary', '@-'],
104
+ [
105
+ '-sS',
106
+ '-X',
107
+ 'POST',
108
+ url,
109
+ '-H',
110
+ 'Content-Type: application/json',
111
+ '-H',
112
+ 'Connection: close',
113
+ ...authArgs,
114
+ '--max-time',
115
+ String(Math.ceil(timeoutMs / 1000)),
116
+ '--data-binary',
117
+ '@-'
118
+ ],
74
119
  { input: body, encoding: 'utf8', timeout: timeoutMs + 5000 }
75
120
  )
76
121
  if (r.error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "5.1.0",
3
+ "version": "5.2.1",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,3 +1,9 @@
1
+ ---
2
+ docgen:
3
+ source: npm/rules/abie/fix.mjs
4
+ crc: 12fc1644
5
+ ---
6
+
1
7
  # fix.mjs
2
8
 
3
9
  ## Огляд