@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.
- package/.claude-template/settings.template.json +22 -0
- package/.pi-template/extensions/n-cursor-adr/docs/index.md +15 -9
- package/CHANGELOG.md +18 -1
- package/bin/n-cursor.js +73 -16
- package/docs/stryker.config.md +6 -0
- package/docs/vitest.config.md +6 -0
- package/lib/docs/llm.md +29 -0
- package/lib/docs/models.md +24 -17
- package/lib/docs/omlx.md +32 -0
- package/lib/llm.mjs +137 -0
- package/lib/omlx.mjs +49 -4
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +6 -0
- package/rules/abie/js/docs/applies.md +6 -0
- package/rules/abie/js/docs/env_dns.md +25 -22
- package/rules/abie/js/docs/firebase_hosting.md +6 -0
- package/rules/abie/js/docs/hc_pairing.md +21 -25
- package/rules/abie/js/docs/ua_http_route.md +27 -19
- package/rules/abie/js/docs/ua_node_selector.md +24 -19
- package/rules/abie/lib/docs/enabled.md +13 -7
- package/rules/abie/lib/docs/env-dns.md +9 -3
- package/rules/abie/lib/docs/hc-yaml.md +6 -0
- package/rules/abie/lib/docs/http-route.md +6 -0
- package/rules/abie/lib/docs/k8s-tree.md +6 -0
- package/rules/abie/lib/docs/kustomization-patches.md +6 -0
- package/rules/abie/lib/docs/overlay-paths.md +6 -0
- package/rules/abie/lib/docs/yaml.md +6 -0
- package/rules/adr/docs/fix.md +6 -0
- package/rules/adr/js/docs/hooks.md +29 -244
- package/rules/bun/docs/fix.md +6 -0
- package/rules/bun/js/docs/layout.md +37 -375
- package/rules/capacitor/docs/fix.md +22 -108
- package/rules/capacitor/js/docs/platforms.md +62 -268
- package/rules/changelog/docs/fix.md +6 -0
- package/rules/changelog/js/docs/consistency.md +36 -383
- package/rules/changelog/lib/docs/package-manifest.md +6 -0
- package/rules/ci4/docs/fix.md +23 -165
- package/rules/ci4/js/docs/marksman_config.md +9 -1
- package/rules/docker/docs/fix.md +6 -0
- package/rules/docker/js/docs/lint.md +55 -239
- package/rules/docker/lib/docs/docker-hadolint.md +6 -0
- package/rules/docker/lib/docs/docker-mirror.md +6 -0
- package/rules/docker/lib/docs/docker-native-addon.md +6 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +6 -0
- package/rules/docker/lint/docs/lint.md +9 -1
- package/rules/efes/docs/fix.md +6 -0
- package/rules/feedback/docs/fix.md +21 -131
- package/rules/ga/docs/fix.md +14 -12
- package/rules/ga/js/docs/lint.md +12 -9
- package/rules/ga/js/docs/workflows.md +20 -19
- package/rules/ga/lint/docs/lint.md +6 -0
- package/rules/graphql/docs/fix.md +6 -0
- package/rules/graphql/js/docs/tooling.md +18 -253
- package/rules/graphql/lib/docs/graphql-gql-scan.md +6 -0
- package/rules/hasura/docs/fix.md +18 -111
- package/rules/image-avif/docs/fix.md +6 -0
- package/rules/image-avif/js/docs/avif_generation.md +6 -0
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +9 -3
- package/rules/js-bun-redis/lib/docs/redis-imports.md +6 -0
- package/rules/js-lint/js/docs/utils_imports.md +6 -0
- package/rules/js-lint-ci/docs/fix.md +7 -1
- package/rules/js-mssql/docs/fix.md +6 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +6 -0
- package/rules/js-run/docs/fix.md +6 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +6 -0
- package/rules/js-run/lib/docs/check-env-scan.md +6 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +6 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +6 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +6 -0
- package/rules/js-run/lib/docs/temporal-scan.md +6 -0
- package/rules/k8s/docs/fix.md +6 -0
- package/rules/k8s/lint/docs/lint.md +6 -0
- package/rules/nginx-default-tpl/docs/fix.md +6 -0
- package/rules/npm-module/js/docs/header_doc_pointer.md +7 -0
- package/rules/npm-module/js/header_doc_pointer.mjs +2 -8
- package/rules/php/docs/fix.md +6 -0
- package/rules/php/lint/docs/lint.md +6 -0
- package/rules/python/docs/fix.md +6 -0
- package/rules/python/lint/docs/lint.md +6 -0
- package/rules/rego/lint/docs/lint.md +6 -0
- package/rules/release/docs/change.md +6 -0
- package/rules/release/docs/fix.md +6 -0
- package/rules/release/docs/release.md +6 -0
- package/rules/release/lib/docs/aggregate.md +6 -0
- package/rules/release/lib/docs/change-file.md +6 -0
- package/rules/release/lib/docs/fallback.md +6 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +6 -0
- package/rules/security/docs/fix.md +7 -1
- package/rules/security/js/docs/lint.md +6 -0
- package/rules/style-lint/docs/fix.md +6 -0
- package/rules/tauri/docs/fix.md +6 -0
- package/rules/test/docs/fix.md +6 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +6 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +6 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +6 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +6 -0
- package/rules/text/docs/fix.md +6 -0
- package/rules/text/lint/docs/lint.md +6 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +6 -0
- package/rules/text/lint/docs/run-shellcheck.md +6 -0
- package/rules/text/lint/docs/run-v8r.md +6 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +6 -0
- package/scripts/coverage-classify/cache.mjs +1 -1
- package/scripts/coverage-classify/docs/apply.md +6 -0
- package/scripts/coverage-classify/docs/cache.md +6 -0
- package/scripts/coverage-classify/docs/prompt.md +6 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +6 -0
- package/scripts/coverage-classify/prompt.mjs +1 -1
- package/scripts/coverage-fix-extract.mjs +1 -1
- package/scripts/coverage-fix.mjs +2 -1
- package/scripts/docs/auto-skills.md +6 -0
- package/scripts/docs/build-agents-commands.md +7 -1
- package/scripts/docs/cli-entry.md +6 -0
- package/scripts/docs/coverage-fix-extract.md +6 -0
- package/scripts/docs/coverage-fix.md +6 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +6 -0
- package/scripts/docs/lint-cli.md +6 -0
- package/scripts/docs/post-tool-use-fix.md +6 -0
- package/scripts/docs/rename-yaml-extensions.md +6 -0
- package/scripts/docs/skills-cli.md +6 -0
- package/scripts/docs/sync-setup-bun-deps-action.md +6 -0
- package/scripts/docs/upgrade-nitra-cursor-and-install.md +6 -0
- package/scripts/docs/worktree-cli.md +6 -0
- package/scripts/lib/docs/assert-project-root.md +6 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +6 -0
- package/scripts/lib/docs/check-reporter.md +6 -0
- package/scripts/lib/docs/diff-added-lines.md +6 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +6 -0
- package/scripts/lib/docs/discover-checkable-rules.md +6 -0
- package/scripts/lib/docs/ensure-tool.md +6 -0
- package/scripts/lib/docs/generated-markdown.md +6 -0
- package/scripts/lib/docs/gha-workflow.md +6 -0
- package/scripts/lib/docs/inline-template-links.md +6 -0
- package/scripts/lib/docs/list-rule-ids.md +6 -0
- package/scripts/lib/docs/load-cursor-config.md +6 -0
- package/scripts/lib/docs/mirror-parity.md +6 -0
- package/scripts/lib/docs/read-n-cursor-config-lite.md +6 -0
- package/scripts/lib/docs/resolve-target-files.md +6 -0
- package/scripts/lib/docs/root-notice.md +6 -0
- package/scripts/lib/docs/rule-meta-helpers.md +6 -0
- package/scripts/lib/docs/rule-meta.md +6 -0
- package/scripts/lib/docs/run-conftest-batch.md +6 -0
- package/scripts/lib/docs/run-lint-step.md +6 -0
- package/scripts/lib/docs/run-rule-cli.md +6 -0
- package/scripts/lib/docs/run-rule.md +6 -0
- package/scripts/lib/docs/run-standard-lint.md +6 -0
- package/scripts/lib/docs/run-standard-rule.md +6 -0
- package/scripts/lib/docs/skill-meta.md +6 -0
- package/scripts/lib/docs/template.md +6 -0
- package/scripts/lib/docs/timing-summary.md +6 -0
- package/scripts/lib/docs/workspaces.md +6 -0
- package/scripts/lib/docs/worktree-notice.md +6 -0
- package/scripts/lib/docs/worktree.md +6 -0
- package/scripts/lib/mirror-parity.mjs +1 -1
- package/scripts/lib/root-notice.mjs +1 -1
- package/scripts/lib/worktree-notice.mjs +5 -5
- package/scripts/lib/worktree.mjs +1 -1
- package/scripts/sync-claude-config.mjs +3 -0
- package/scripts/utils/docs/ast-scan-utils.md +6 -0
- package/scripts/utils/docs/ensure-gitignore-entries.md +6 -0
- package/scripts/utils/docs/find-package-json-paths.md +6 -0
- package/scripts/utils/docs/lock-cache-dir.md +6 -0
- package/scripts/utils/docs/pass.md +6 -0
- package/scripts/utils/docs/resolve-cargo-manifest.md +6 -0
- package/scripts/utils/docs/resolve-cmd.md +6 -0
- package/scripts/utils/docs/resolve-js-root.md +6 -0
- package/scripts/utils/docs/test-helpers.md +6 -0
- package/scripts/utils/docs/walk-cache.md +6 -0
- package/scripts/utils/docs/walkDir.md +6 -0
- package/scripts/utils/docs/worktree-fingerprint.md +6 -0
- package/scripts/utils/resolve-js-root.mjs +1 -1
- package/skills/doc-aggregate/SKILL.md +129 -0
- package/skills/doc-aggregate/js/docgen-ignore.mjs +9 -0
- package/skills/{docgen → doc-aggregate}/js/docgen-scan.mjs +22 -67
- package/skills/doc-aggregate/js/docs/docgen-ignore.md +21 -0
- package/skills/doc-files/SKILL.md +100 -0
- package/skills/doc-files/js/docgen-crc.mjs +164 -0
- package/skills/{docgen → doc-files}/js/docgen-extract-anchors.mjs +48 -13
- package/skills/{docgen → doc-files}/js/docgen-extract.mjs +39 -10
- package/skills/doc-files/js/docgen-files-batch.mjs +181 -0
- package/skills/doc-files/js/docgen-gen.mjs +336 -0
- package/skills/{docgen → doc-files}/js/docgen-prompts.mjs +65 -50
- package/skills/doc-files/js/docgen-scan.mjs +298 -0
- package/skills/doc-files/js/docs/docgen-crc.md +32 -0
- package/skills/doc-files/js/docs/docgen-extract-anchors.md +27 -0
- package/skills/doc-files/js/docs/docgen-extract.md +29 -0
- package/skills/doc-files/js/docs/docgen-files-batch.md +25 -0
- package/skills/doc-files/js/docs/docgen-gen.md +30 -0
- package/skills/doc-files/js/docs/docgen-prompts.md +32 -0
- package/skills/doc-files/js/docs/docgen-scan.md +25 -0
- package/skills/doc-files/js/units-js.mjs +139 -0
- package/skills/doc-files/js/units.mjs +19 -0
- package/skills/doc-files/meta.json +1 -0
- package/skills/fix/js/docs/llm-worker.md +6 -0
- package/skills/fix/js/docs/orchestrator.md +6 -0
- package/skills/fix/js/llm-worker.mjs +3 -3
- package/skills/fix/js/orchestrator.mjs +1 -1
- package/skills/start-check/js/check.mjs +5 -3
- package/skills/start-check/js/docs/check.md +6 -0
- package/skills/docgen/SKILL.md +0 -224
- package/skills/docgen/bench/etalon/firebase_hosting.md +0 -19
- package/skills/docgen/bench/etalon/k8s-tree.md +0 -24
- package/skills/docgen/bench/etalon/overlay-paths.md +0 -24
- package/skills/docgen/js/docgen-batch-omlx.mjs +0 -82
- package/skills/docgen/js/docgen-batch.mjs +0 -95
- package/skills/docgen/js/docgen-compare-pi-vs-direct.mjs +0 -95
- package/skills/docgen/js/docgen-gen.mjs +0 -306
- package/skills/docgen/js/docs/docgen-extract.md +0 -28
- package/skills/docgen/js/docs/docgen-gen.md +0 -41
- package/skills/docgen/js/docs/docgen-ignore.md +0 -24
- package/skills/docgen/js/docs/docgen-prompts.md +0 -24
- package/skills/docgen/js/docs/docgen-scan.md +0 -48
- /package/skills/{docgen → doc-aggregate}/meta.json +0 -0
- /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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 (
|
|
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
|
|
29
|
-
* `npx \@nitra/cursor
|
|
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(
|
|
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`-команди `
|
|
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,
|
|
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 '
|
|
1769
|
-
// n-cursor
|
|
1770
|
-
//
|
|
1771
|
-
//
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
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
|
|
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,
|
|
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
|
}
|
package/docs/stryker.config.md
CHANGED
package/docs/vitest.config.md
CHANGED
package/lib/docs/llm.md
ADDED
|
@@ -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` означає «машина зайнята, спробуй пізніше», а не дефект моделі чи конвеєра.
|
package/lib/docs/models.md
CHANGED
|
@@ -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
|
-
Файл визначає
|
|
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 —
|
|
22
|
-
LOCAL_AVG —
|
|
23
|
-
LOCAL_MAX —
|
|
24
|
-
CLOUD_MIN —
|
|
25
|
-
CLOUD_AVG —
|
|
26
|
-
CLOUD_MAX —
|
|
27
|
-
resolveModel —
|
|
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
|
|
package/lib/docs/omlx.md
ADDED
|
@@ -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
|
-
[
|
|
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