@nitra/cursor 3.7.0 → 3.9.0

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.9.0] - 2026-06-01
4
+
5
+ ### Changed
6
+
7
+ - k8s: канонічний образ hasura/graphql-engine → v2.49.0.ubuntu.amd64 (ubuntu-база містить pg_dump 18, на відміну від ubi-варіанту)
8
+
9
+ ### Removed
10
+
11
+ - k8s: видалено мертву JS-перевірку образа hasura (deploymentHasuraGraphqlEngineImageViolation + набір HASURA_GRAPHQL_ENGINE_ALLOWED_IMAGES) — пер-документна перевірка делегована rego-пакету k8s.manifest, тег тепер має єдине джерело істини (allowed_hasura_images)
12
+
13
+ ## [3.8.0] - 2026-06-01
14
+
15
+ ### Added
16
+
17
+ - flow: контракт — advanced elicitation (меню технік поглиблення вимог у фазі spec: Expand/Contract, Critique&Refine, Identify Risks, Tree-of-Thoughts, Stakeholder Roundtable, Self-Consistency; Elicitation History у спеці)
18
+
3
19
  ## [3.7.0] - 2026-06-01
4
20
 
5
21
  ### Added
package/bin/n-cursor.js CHANGED
@@ -100,6 +100,7 @@ import { runLintK8s } from '../rules/k8s/lint/lint.mjs'
100
100
  import { runLintRego } from '../rules/rego/lint/lint.mjs'
101
101
  import { runLintTextCli } from '../rules/text/lint/lint.mjs'
102
102
  import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
103
+ import { syncGitignoreWorktree } from '../scripts/lib/sync-gitignore-worktree.mjs'
103
104
  import { upgradeNitraCursorToLatestAndBunInstall } from '../scripts/upgrade-nitra-cursor-and-install.mjs'
104
105
  import { runRenameYamlExtensionsCli } from './rename-yaml-extensions.mjs'
105
106
  import { runSkillsCli } from '../scripts/skills-cli.mjs'
@@ -715,8 +716,7 @@ async function syncClaudeMd(ignore) {
715
716
  lines.push(`@${RULES_DIR}/${mdcFile}`)
716
717
  }
717
718
 
718
- lines.push(...buildClaudeLintParallelismSectionLines())
719
- lines.push(...buildClaudeWorktreeEnforcementSectionLines())
719
+ lines.push(...buildClaudeLintParallelismSectionLines(), ...buildClaudeWorktreeEnforcementSectionLines())
720
720
 
721
721
  const skillsSectionLines = await buildClaudeSkillsSectionLines()
722
722
  lines.push(...skillsSectionLines)
@@ -1437,6 +1437,11 @@ async function runSync() {
1437
1437
  }
1438
1438
  })
1439
1439
 
1440
+ await runSyncStep('❌ Не вдалося оновити .gitignore (worktree): ', async () => {
1441
+ const { written } = await syncGitignoreWorktree(cwd())
1442
+ if (written) console.log('🌳 .gitignore (worktree): додано .worktrees/')
1443
+ })
1444
+
1440
1445
  console.log(`\n✨ Готово: ${successCount} завантажено, ${failCount} з помилками\n`)
1441
1446
  if (failCount > 0) {
1442
1447
  throw new Error(`Не вдалося завантажити ${failCount} з ${rules.length} правил`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "3.7.0",
3
+ "version": "3.9.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -35,6 +35,20 @@ Composer, Claude Code) працює **Пасивний Турнікет**: ти,
35
35
  - **agent↔agent:** `npx @nitra/cursor flow spec --panel` — панель персон
36
36
  (architect/skeptic/tester) → суддя-синтез; презентуй синтез людині.
37
37
 
38
+ **Поглиблення (advanced elicitation, за потреби).** Якщо чернетка дизайну
39
+ «сира» чи є непевність — запропонуй людині одну з технік (по одній за раз),
40
+ застосуй обрану, повтори до «досить»:
41
+
42
+ - **Expand / Contract** — розгорнути деталь, що бракує, або згорнути зайве до суті;
43
+ - **Critique & Refine** — самокритика чернетки (слабкі місця, припущення), тоді доопрацювання;
44
+ - **Identify Risks** — явно перелічити ризики/граничні випадки (наповнює поле `risk:` у frontmatter);
45
+ - **Tree-of-Thoughts** — кілька гілок рішення паралельно, обрати найкращу з обґрунтуванням;
46
+ - **Stakeholder Roundtable** — пройтись поглядами ролей (user / ops / security / maintainer);
47
+ - **Self-Consistency** — 2-3 незалежні версії відповіді, звірити на суперечності.
48
+
49
+ Що застосовано — фіксуй коротко в секції `## Elicitation History` спеки
50
+ (traceability рішень).
51
+
38
52
  Збережи дизайн → `docs/specs/<date>-<slug>.md` (`kind: nitra-spec`,
39
53
  `plan: null`), тоді зафіксуй:
40
54
 
@@ -19,7 +19,8 @@
19
19
  * = **`"512Mi"`**. Поле **`imagePullPolicy`**
20
20
  * не перевіряється — діють типові правила Kubernetes (`:latest` або коли тег не вказано → **Always**,
21
21
  * інші теги → **IfNotPresent**). Якщо серед **`containers`** / **`initContainers`** є образ
22
- * **`hasura/graphql-engine`**, дозволено лише пін **`HASURA_GRAPHQL_ENGINE_IMAGE`** (див. k8s.mdc).
22
+ * **`hasura/graphql-engine`**, дозволено лише канонічний тег зі списку `allowed_hasura_images`
23
+ * у rego-пакеті `k8s.manifest` (`policy/manifest/manifest.rego`) — пер-документна перевірка делегована rego.
23
24
  *
24
25
  * **Namespace і Kustomize:** YAML у **`…/k8s/base/`** (окрім імені **`kustomization.yaml`**)
25
26
  * завжди має **непорожній** **`metadata.namespace`** у відповідних документах (узгоджено з dev у репозиторії),
@@ -151,20 +152,6 @@ const YAML_LS_MODELINE_RE = /^# yaml-language-server: \$schema=.*\n/
151
152
 
152
153
  const YANNH_PIN = 'v1.33.9-standalone-strict'
153
154
 
154
- /**
155
- * Дозволений образ **hasura/graphql-engine** у Deployment (узгоджено з k8s.mdc).
156
- * Еквівалент **`docker.io/…`** також приймається.
157
- */
158
- export const HASURA_GRAPHQL_ENGINE_IMAGE = 'hasura/graphql-engine:v2.48.15.ubi.amd64'
159
-
160
- /**
161
- Набір прийнятних рядків `image` без digest (`@sha256:…`).
162
- */
163
- const HASURA_GRAPHQL_ENGINE_ALLOWED_IMAGES = new Set([
164
- HASURA_GRAPHQL_ENGINE_IMAGE,
165
- `docker.io/${HASURA_GRAPHQL_ENGINE_IMAGE}`
166
- ])
167
-
168
155
  /**
169
156
  * Чи відносний POSIX-шлях від кореня репо вказує на YAML під **`…/k8s/…/base/…`** (після сегмента **`k8s`** у шляху
170
157
  * є каталог **`base`**). Тут очікуються маніфести шару **base**, включно з будь-яким файлом із **`kind: Deployment`**
@@ -2406,75 +2393,6 @@ function isHasuraGraphqlEngineImageRef(image) {
2406
2393
  return HASURA_GRAPHQL_ENGINE_RE.test(s)
2407
2394
  }
2408
2395
 
2409
- /**
2410
- * Перевірка образу Hasura для одного контейнера у списку **containers** / **initContainers**.
2411
- * @param {string} list ім’я поля для повідомлення (`containers` / `initContainers`)
2412
- * @param {unknown} c елемент масиву
2413
- * @param {number} i індекс
2414
- * @returns {string | null} текст порушення або null
2415
- */
2416
- function hasuraGraphqlEngineViolationForOneContainer(list, c, i) {
2417
- const label =
2418
- typeof c === 'object' && c !== null && !Array.isArray(c) && typeof c.name === 'string' && c.name !== ''
2419
- ? c.name
2420
- : `#${i + 1}`
2421
- if (c === null || c === undefined || typeof c !== 'object' || Array.isArray(c)) {
2422
- return null
2423
- }
2424
- const cont = /** @type {Record<string, unknown>} */ (c)
2425
- const image = cont.image
2426
- if (typeof image !== 'string' || image.trim() === '' || !isHasuraGraphqlEngineImageRef(image)) {
2427
- return null
2428
- }
2429
- const normalized = stripImageDigest(image)
2430
- if (!HASURA_GRAPHQL_ENGINE_ALLOWED_IMAGES.has(normalized)) {
2431
- return `${list} "${label}": образ hasura/graphql-engine має бути ${HASURA_GRAPHQL_ENGINE_IMAGE} (зараз: ${image}) (див. k8s.mdc)`
2432
- }
2433
- return null
2434
- }
2435
-
2436
- /**
2437
- * Перевіряє масив **containers** / **initContainers** на зафіксований образ Hasura.
2438
- * @param {string} list **containers** або **initContainers** (для тексту помилки)
2439
- * @param {unknown} containers значення поля з маніфесту
2440
- * @returns {string | null} текст порушення або null
2441
- */
2442
- function hasuraGraphqlEngineViolationInContainerList(list, containers) {
2443
- if (!Array.isArray(containers)) return null
2444
- for (const [i, c] of containers.entries()) {
2445
- const v = hasuraGraphqlEngineViolationForOneContainer(list, c, i)
2446
- if (v !== null) {
2447
- return v
2448
- }
2449
- }
2450
- return null
2451
- }
2452
-
2453
- /**
2454
- * Чи порушує **Deployment** вимогу щодо зафіксованого образу **hasura/graphql-engine** (k8s.mdc).
2455
- * @param {unknown} manifest корінь YAML-документа
2456
- * @returns {string | null} текст порушення або null, якщо не Deployment / образу немає / ок
2457
- */
2458
- export function deploymentHasuraGraphqlEngineImageViolation(manifest) {
2459
- if (manifest === null || manifest === undefined || typeof manifest !== 'object' || Array.isArray(manifest))
2460
- return null
2461
- const rec = /** @type {Record<string, unknown>} */ (manifest)
2462
- if (rec.kind !== 'Deployment') return null
2463
- const spec = rec.spec
2464
- if (spec === null || spec === undefined || typeof spec !== 'object' || Array.isArray(spec)) return null
2465
- const template = /** @type {Record<string, unknown>} */ (spec).template
2466
- if (template === null || template === undefined || typeof template !== 'object' || Array.isArray(template))
2467
- return null
2468
- const podSpecRaw = /** @type {Record<string, unknown>} */ (template).spec
2469
- if (podSpecRaw === null || podSpecRaw === undefined || typeof podSpecRaw !== 'object' || Array.isArray(podSpecRaw))
2470
- return null
2471
- const podSpec = /** @type {Record<string, unknown>} */ (podSpecRaw)
2472
-
2473
- const main = hasuraGraphqlEngineViolationInContainerList('containers', podSpec.containers)
2474
- if (main !== null) return main
2475
- return hasuraGraphqlEngineViolationInContainerList('initContainers', podSpec.initContainers)
2476
- }
2477
-
2478
2396
  /**
2479
2397
  * Чи у списку контейнерів є хоча б один з образом **hasura/graphql-engine** (будь-який тег).
2480
2398
  * @param {unknown} containers значення **containers** / **initContainers** із podSpec
package/rules/k8s/k8s.mdc CHANGED
@@ -152,7 +152,7 @@ patches:
152
152
 
153
153
  Поле **`imagePullPolicy`** скрипт **не** перевіряє (залишається політиці Kubernetes за тегом образу).
154
154
 
155
- Образ **`hasura/graphql-engine`**: дозволений лише канонічний тег із константи **`HASURA_GRAPHQL_ENGINE_IMAGE`** у **`rules/k8s/fix.mjs`** (допускається префікс **`docker.io/`**); решта — помилка **check k8s**.
155
+ Образ **`hasura/graphql-engine`**: дозволений лише канонічний тег зі списку **`allowed_hasura_images`** у rego-пакеті **`k8s.manifest`** (`policy/manifest/manifest.rego` — єдине джерело істини; допускається префікс **`docker.io/`**); решта — помилка **check k8s**.
156
156
 
157
157
  ### HTTPRoute для Deployment з `hasura/graphql-engine`
158
158
 
@@ -44,13 +44,14 @@ forbidden_service_annotations := {
44
44
  "cloud.google.com/backend-config",
45
45
  }
46
46
 
47
- # Дозволені посилання на образ `hasura/graphql-engine` (узгоджено з
48
- # `HASURA_GRAPHQL_ENGINE_IMAGE` у `rules/k8s/fix.mjs`). Зараз один канонічний тег
49
- # у двох варіантах префіксу (із `docker.io/` і без). Digest (`@sha256:…`)
47
+ # Дозволені посилання на образ `hasura/graphql-engine`. Це **єдине** джерело
48
+ # істини для канонічного тега (JS-копія `HASURA_GRAPHQL_ENGINE_IMAGE` видалена
49
+ # пер-документна перевірка делегована цьому rego-пакету). Зараз один канонічний
50
+ # тег у двох варіантах префіксу (із `docker.io/` і без). Digest (`@sha256:…`)
50
51
  # відрізається перед звіркою.
51
52
  allowed_hasura_images := {
52
- "hasura/graphql-engine:v2.48.15.ubi.amd64",
53
- "docker.io/hasura/graphql-engine:v2.48.15.ubi.amd64",
53
+ "hasura/graphql-engine:v2.49.0.ubuntu.amd64",
54
+ "docker.io/hasura/graphql-engine:v2.49.0.ubuntu.amd64",
54
55
  }
55
56
 
56
57
  # Канонічне значення `topologyKey` для `topologySpreadConstraints` (k8s.mdc).
@@ -161,7 +162,7 @@ deny contains msg if {
161
162
 
162
163
  # ── deny: Deployment — образ hasura/graphql-engine з білого списку ────────
163
164
  #
164
- # Spec вимагає рівно тег із константи `HASURA_GRAPHQL_ENGINE_IMAGE`
165
+ # Spec вимагає рівно тег зі списку `allowed_hasura_images` (вище)
165
166
  # (з опційним префіксом `docker.io/`). Digest `@sha256:…` у поточних правилах
166
167
  # відрізається перед порівнянням (k8s.mdc допускає, але не вимагає його).
167
168
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Гарантує, що кореневий `.gitignore` проєкту ігнорує локальні git-worktree
3
+ * (`.worktrees/`). Викликається з дефолтного sync (`npx \@nitra/cursor`) окремим
4
+ * top-level кроком — поза `syncClaudeConfig`, бо `.worktrees/` — артефакт
5
+ * завжди-активного flow/worktree-tooling, а не Claude/Cursor-конфігу.
6
+ *
7
+ * Один запис `.worktrees/` покриває каталог worktree та всі sibling-файли в ньому
8
+ * (`<branch>.flow.json`, `.events.jsonl`, `<name>.md`, `.flow-lock-*`). Запис
9
+ * безумовний (без гейта за `.n-cursor.json`-правилами): продюсер артефактів —
10
+ * завжди-активний flow, тож гейт міг би розсинхронитися з ним.
11
+ *
12
+ * Делегує наявній idempotent+append-only утиліті `ensureGitignoreEntries` (header-
13
+ * секція, не перезаписує/не видаляє наявні рядки; створює `.gitignore`, якщо нема).
14
+ */
15
+ import { ensureGitignoreEntries } from '../utils/ensure-gitignore-entries.mjs'
16
+
17
+ /** Header-секція для керованого запису у `.gitignore`. */
18
+ const WORKTREE_SECTION_LABEL = '@nitra/cursor — локальні git-worktree, не коміти'
19
+
20
+ /**
21
+ * Дописує `.worktrees/` у кореневий `.gitignore`, якщо рядка ще немає.
22
+ * @param {string} projectRoot корінь проєкту-споживача (де `.gitignore`)
23
+ * @returns {Promise<{ written: boolean }>} чи був дописаний рядок
24
+ */
25
+ export async function syncGitignoreWorktree(projectRoot) {
26
+ const { added } = await ensureGitignoreEntries(projectRoot, ['.worktrees/'], WORKTREE_SECTION_LABEL)
27
+ return { written: added.length > 0 }
28
+ }
@@ -75,8 +75,7 @@ const CYRILLIC_TRANSLIT = new Map(
75
75
  * @returns {string} транслітерований текст
76
76
  */
77
77
  function transliterate(value) {
78
- return [...value.toLowerCase()]
79
- .map((char) => CYRILLIC_TRANSLIT.get(char) ?? char)
78
+ return Array.from(value.toLowerCase(), (char) => CYRILLIC_TRANSLIT.get(char) ?? char)
80
79
  .join("");
81
80
  }
82
81
 
@@ -92,9 +91,9 @@ function deriveSuffix(content) {
92
91
  .trim()
93
92
  .replace(/^n-/u, "")
94
93
  .normalize("NFKD")
95
- .replace(/[\u0300-\u036f]/gu, "")
96
- .replace(/[^a-z0-9]+/gu, "-")
97
- .replace(/^-+|-+$/gu, "");
94
+ .replaceAll(/[\u0300-\u036F]/gu, "")
95
+ .replaceAll(/[^a-z0-9]+/gu, "-")
96
+ .replaceAll(/^-+|-+$/gu, "");
98
97
 
99
98
  return (
100
99
  (slug || FALLBACK_SUFFIX).slice(0, 10).replace(/-+$/u, "") ||
@@ -27,7 +27,7 @@ export function sanitizeBranch(branch) {
27
27
  const sanitized = branch
28
28
  .trim()
29
29
  .replace(UNSAFE_PATH_CHARS_RE, '-')
30
- .replace(/^-+|-+$/gu, '')
30
+ .replaceAll(/^-+|-+$/gu, '')
31
31
  if (sanitized === '') {
32
32
  throw new Error(`worktree: імʼя гілки "${branch}" не містить допустимих символів`)
33
33
  }