@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 +16 -0
- package/bin/n-cursor.js +7 -2
- package/package.json +1 -1
- package/rules/flow/flow.mdc +14 -0
- package/rules/k8s/js/manifests.mjs +2 -84
- package/rules/k8s/k8s.mdc +1 -1
- package/rules/k8s/policy/manifest/manifest.rego +7 -6
- package/scripts/lib/sync-gitignore-worktree.mjs +28 -0
- package/scripts/lib/worktree-notice.mjs +4 -5
- package/scripts/lib/worktree.mjs +1 -1
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
package/rules/flow/flow.mdc
CHANGED
|
@@ -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`**, дозволено лише
|
|
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`**: дозволений лише канонічний тег
|
|
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
|
-
#
|
|
49
|
-
#
|
|
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.
|
|
53
|
-
"docker.io/hasura/graphql-engine:v2.
|
|
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 вимагає рівно тег
|
|
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
|
|
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
|
-
.
|
|
96
|
-
.
|
|
97
|
-
.
|
|
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, "") ||
|
package/scripts/lib/worktree.mjs
CHANGED
|
@@ -27,7 +27,7 @@ export function sanitizeBranch(branch) {
|
|
|
27
27
|
const sanitized = branch
|
|
28
28
|
.trim()
|
|
29
29
|
.replace(UNSAFE_PATH_CHARS_RE, '-')
|
|
30
|
-
.
|
|
30
|
+
.replaceAll(/^-+|-+$/gu, '')
|
|
31
31
|
if (sanitized === '') {
|
|
32
32
|
throw new Error(`worktree: імʼя гілки "${branch}" не містить допустимих символів`)
|
|
33
33
|
}
|