@nitra/cursor 12.13.0 → 12.15.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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [12.15.0] - 2026-06-26
4
+
5
+ ### Changed
6
+
7
+ - Видалено скіл start-check та пов'язані файли
8
+
9
+ ## [12.14.0] - 2026-06-26
10
+
11
+ ### Changed
12
+
13
+ - Заміна picomatch на ignore для обробки glob-ів у docgen та resolve-target-files
14
+
3
15
  ## [12.13.0] - 2026-06-26
4
16
 
5
17
  ### Changed
@@ -5,7 +5,7 @@
5
5
  Файл `npm/bin/n-cursor.js` — це виконуваний скрипт (shebang `#!/usr/bin/env node`), що слугує єдиною точкою входу CLI пакета `@nitra/cursor`. Скрипт виконує дві ролі:
6
6
 
7
7
  1. **Синхронізатор пакетних артефактів у проєкті-споживачі** — без аргументів копіює `.mdc`-правила, скіли, slash-команди, генерує `AGENTS.md`, `CLAUDE.md`, синхронізує `.claude/settings.json`, `.cursor/hooks.json`, composite GitHub Action `setup-bun-deps`, `.pi/skills`, а також `.gitignore` для `.worktrees/`.
8
- 2. **Маршрутизатор підкоманд** — диспатчить `fix`, `check`, `rename-yaml-extensions`, `post-tool-use-fix`, `lint`, `lint-ci`, `lint-doc-files`, `fix-doc-files`, `coverage`, `coverage-fix`, `analyze-escalation`, `taze`, `start-check`, `change`, `release`, `skill`, `worktree`, `trace`, `doc-aggregate`, `adr-normalize-local` у відповідні внутрішні модулі пакета.
8
+ 2. **Маршрутизатор підкоманд** — диспатчить `rename-yaml-extensions`, `hook`, `lint`, `analyze-escalation`, `taze`, `release`, `skill`, `trace`, `adr-normalize-local`, `doc-aggregate` у відповідні внутрішні модулі пакета.
9
9
 
10
10
  Скрипт — ES-модуль (`import` синтаксис). Виконує реальні файлові операції в `cwd()` і у каталогах пакету (`BUNDLED_PACKAGE_ROOT`). Усі шляхи відносно поточної робочої директорії проєкту-споживача.
11
11
 
@@ -20,7 +20,6 @@
20
20
  - `npx @nitra/cursor lint` — data-driven оркестратор lint+конформності: `--full` (весь репо, включно з `full`-правилами), `--read-only` (CI, нуль мутацій); без прапорів — per-file дельта vs origin.
21
21
  - `npx @nitra/cursor lint-ci` — те саме у CI-режимі.
22
22
  - `npx @nitra/cursor coverage [--fix] [--changed]` — оркестратор покриття та мутаційного тестування.
23
- - `npx @nitra/cursor change` — створення change-файлу.
24
23
  - `npx @nitra/cursor release` — реліз-команда.
25
24
  - `npx @nitra/cursor skill list|taze|cursor|claude …` — керування скілами (промпт на stdout, виклик Cursor/Claude CLI).
26
25
  - `npx @nitra/cursor worktree …` — керування git-worktree.
@@ -462,7 +461,6 @@ try {
462
461
  - `'lint'` → `runLint({ full, readOnly, rules })` (прапори `--full`, `--read-only`; позиційні аргументи — фільтр правил конформності).
463
462
  - `'lint-ci'` → `runLint({ ci: true })`.
464
463
  - `'coverage'` → динамічний import `../rules/test/coverage/coverage.mjs`, виклик `runCoverageCli({ fix: args.includes('--fix'), changed: args.includes('--changed') })`.
465
- - `'change'` → динамічний import `../rules/release/change.mjs` → `runChangeCli(args)`.
466
464
  - `'release'` → динамічний import `../rules/release/release.mjs` → `runReleaseCli(args)`.
467
465
  - `'skill'` → `runSkillsCli(args)` (синхронний).
468
466
  - `'worktree'` → `runWorktreeCli(args)`.
@@ -519,7 +517,6 @@ try {
519
517
  ### Динамічні (lazy) залежності
520
518
 
521
519
  - `../rules/test/coverage/coverage.mjs` — `runCoverageCli`.
522
- - `../rules/release/change.mjs` — `runChangeCli`.
523
520
  - `../rules/release/release.mjs` — `runReleaseCli`.
524
521
  - `../scripts/dispatcher/trace.mjs` — `runTraceCli`.
525
522
  - `../skills/docgen/js/docgen-scan.mjs` — `runDocgenScanCli`, `runDocgenModulesCli`.
package/bin/n-cursor.js CHANGED
@@ -1546,15 +1546,6 @@ try {
1546
1546
 
1547
1547
  break
1548
1548
  }
1549
- case 'start-check': {
1550
- // n-cursor start-check scan|run — детермінована частина smoke-перевірки для
1551
- // скілу n-start-check: scan виявляє воркспейси зі start і їх тип, run запускає
1552
- // один із grace-таймаутом і класифікує OK/FAIL. Агент лишається з діагностикою.
1553
- const { runStartCheckCli } = await import('../skills/start-check/js/check.mjs')
1554
- process.exitCode = await runStartCheckCli(args)
1555
-
1556
- break
1557
- }
1558
1549
  case 'release': {
1559
1550
  const { runReleaseCli } = await import('../rules/release/release.mjs')
1560
1551
  process.exitCode = await runReleaseCli(args)
@@ -1584,7 +1575,7 @@ try {
1584
1575
  default: {
1585
1576
  console.error(`❌ Невідома команда: ${command}`)
1586
1577
  console.error(
1587
- ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, hook, adr-normalize-local, lint (включно зі scope: lint ga|rego|k8s|docker|text), analyze-escalation, taze, start-check, release, skill, doc-aggregate`
1578
+ ` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, hook, adr-normalize-local, lint (включно зі scope: lint ga|rego|k8s|docker|text), analyze-escalation, taze, release, skill, doc-aggregate`
1588
1579
  )
1589
1580
  process.exitCode = 1
1590
1581
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "12.13.0",
3
+ "version": "12.15.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -55,6 +55,7 @@
55
55
  "dependencies": {
56
56
  "@7n/mt": "^0.5.1",
57
57
  "globby": "^16.0.0",
58
+ "ignore": "^7.0.5",
58
59
  "oxc-parser": "^0.137.0",
59
60
  "picomatch": "^4.0.4",
60
61
  "smol-toml": "^1.7.0",
@@ -1,5 +1,5 @@
1
1
  /** @see ./docs/docgen-ignore.md */
2
- import picomatch from 'picomatch'
2
+ import ignore from 'ignore'
3
3
 
4
4
  /** Базовий список glob-ів для `docgen` ignore. */
5
5
  export const DOCGEN_IGNORE_GLOBS = Object.freeze([
@@ -22,7 +22,7 @@ export const DOCGEN_IGNORE_GLOBS = Object.freeze([
22
22
  'npm/rules/k8s/js/manifests.mjs'
23
23
  ])
24
24
 
25
- const IGNORE_MATCHERS = DOCGEN_IGNORE_GLOBS.map(glob => picomatch(glob, { dot: true }))
25
+ const ig = ignore().add(DOCGEN_IGNORE_GLOBS)
26
26
 
27
27
  /**
28
28
  * Нормалізує відносний шлях до posix-формату для glob-matching.
@@ -36,7 +36,7 @@ function toPosixRelPath(relPath) {
36
36
  /**
37
37
  * Перевіряє, чи шлях має бути пропущений `docgen`.
38
38
  * Для `kind = 'dir'` це працює і на піддерево каталогу, тож glob на кшталт
39
- * `**\\/demo/**` спрацьовує на `demo/x` під час рекурсивного обходу.
39
+ * `**\/demo/**` спрацьовує на `demo/x` під час рекурсивного обходу.
40
40
  * @param {string} relPath відносний шлях від кореня проєкту
41
41
  * @param {'path'|'dir'} [kind] тип перевірки (за замовчуванням `'path'`)
42
42
  * @returns {boolean} `true`, якщо шлях ігнорується
@@ -47,7 +47,8 @@ export function isDocgenIgnored(relPath, kind = 'path') {
47
47
  }
48
48
  const posixRelPath = toPosixRelPath(relPath)
49
49
  if (kind === 'dir') {
50
- return IGNORE_MATCHERS.some(match => match(posixRelPath) || match(`${posixRelPath}/__docgen__`))
50
+ // `**/demo/**` не матчить `demo` напряму — перевіряємо фіктивний файл всередині
51
+ return ig.ignores(posixRelPath) || ig.ignores(`${posixRelPath}/__docgen__`)
51
52
  }
52
- return IGNORE_MATCHERS.some(match => match(posixRelPath))
53
+ return ig.ignores(posixRelPath)
53
54
  }
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: docgen-ignore.mjs
4
4
  resource: npm/rules/doc-files/js/docgen-ignore.mjs
5
5
  docgen:
6
- crc: c17cd785
6
+ crc: 00687334
7
7
  score: 100
8
8
  ---
9
9
 
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: resolve-target-files.mjs
4
4
  resource: npm/scripts/lib/resolve-target-files.mjs
5
5
  docgen:
6
- crc: a3b361d9
6
+ crc: 4d3546e6
7
7
  ---
8
8
 
9
9
  Цей файл відповідає за перевірку наявності файлів, що відповідають певним правилам, вказаним у файлах `policy/<name>/target.json`. Він створює список файлів для подальшого використання, використовуючи або конкретні відносні шляхи, або обхід каталогу з використанням шаблонів та ігнорування файлів. Це забезпечує узгодженість та контроль над файлами, які потрібно обробити, відповідно до заданих політик.
@@ -16,7 +16,7 @@ docgen:
16
16
  4. **Завантаження ігнорування:** Завантажує список абсолютних шляхів, які слід ігнорувати, з конфігурації `.n-cursor.json:ignore`.
17
17
  5. **Кешування обходу:** Використовує кеш обходу дерева (Map) для запобігання повторному обходу одного й того ж набору ігнорування. Якщо обхід вже виконано для заданого набору ігнорування, повертає результат з кешу.
18
18
  6. **Обхід дерева:** Якщо обхід не кешований або кеш порожній, виконує обхід дерева файлів від заданого кореня, використовуючи `walkDir`. Під час обходу генерує відносні шляхи файлів від кореня.
19
- 7. **Фільтрація за масками:** Для кожного відносного шляху файлу застосовує маски `walkGlob` за допомогою `picomatch`. Фільтрує файли, які відповідають маскам, та виключає ті, що відповідають негативним маскам.
19
+ 7. **Фільтрація за масками:** Для кожного відносного шляху файлу застосовує маски `walkGlob` за допомогою `ignore`. Фільтрує файли, які відповідають маскам, та виключає ті, що відповідають негативним маскам.
20
20
  8. **Збіг з ігноруванням:** Перевіряє, чи файл не входить до списку ігнорування.
21
21
  9. **Збіг шляху:** Перетворює відносні шляхи файлів у абсолютні шляхи, додавши корень.
22
22
  10. **Повернення результатів:** Повертає масив абсолют
@@ -29,7 +29,7 @@ resolveTargetFiles — Знаходить файли, що вказані в `ta
29
29
 
30
30
  - **Контракт на поліси:** Зчитуються лише файли з репозиторію.
31
31
  - **`single`:** Якщо `existsSync`, повертається список файлів, що відповідають `single`. Інакше, повертається порожній список.
32
- - **`walkGlob`:** Використовується picomatch для обробки glob-шаблонів відносно шляхів, отриманих з обходу `walkDir` від `root`.
32
+ - **`walkGlob`:** Використовується ignore для обробки glob-шаблонів відносно шляхів, отриманих з обходу `walkDir` від `root`.
33
33
  - **Ігнорування:** Підтримується ігнорування файлів за допомогою `.n-cursor.json:ignore` та кешування шляхів ігнорування у `walkCache`.
34
34
  - **Кешування:** Результати обходу кешуються для повторного використання при однаковому наборі ігнорувань.
35
35
  - **Path-traversal:** При розв'язанні шляхів у формі `single` виникає помилка.
@@ -4,7 +4,7 @@
4
4
  * Дві форми у `target.json:files`:
5
5
  * - `{ "single": "<rel>" }` — конкретний відносний шлях. Якщо `existsSync(root/single)` → `[single]`;
6
6
  * інакше `[]` (caller сам вирішує fail vs silent skip за `required`).
7
- * - `{ "walkGlob": <glob | glob[]> }` — picomatch проти posix-відносних шляхів, отриманих обходом
7
+ * - `{ "walkGlob": <glob | glob[]> }` — `ignore` проти posix-відносних шляхів, отриманих обходом
8
8
  * `walkDir` від `root` із загальними skip-ами та `.n-cursor.json:ignore`. Обхід кешований у
9
9
  * `walkCache` (Map ключ — підпис ignorePaths) — повторні таргети з тим самим набором ignore
10
10
  * перевикористовують список без нового readdir.
@@ -15,7 +15,7 @@
15
15
  import { existsSync } from 'node:fs'
16
16
  import { isAbsolute, join, normalize, relative, sep } from 'node:path'
17
17
 
18
- import picomatch from 'picomatch'
18
+ import ignore from 'ignore'
19
19
 
20
20
  import { loadCursorIgnorePaths } from './load-cursor-config.mjs'
21
21
  import { walkDir } from '../utils/walkDir.mjs'
@@ -96,14 +96,8 @@ export async function resolveTargetFiles(filesSpec, root, walkCache) {
96
96
  const ignorePaths = await loadCursorIgnorePaths(root)
97
97
  const all = await getAllFilesCached(root, ignorePaths, walkCache)
98
98
  const globs = Array.isArray(filesSpec.walkGlob) ? filesSpec.walkGlob : [filesSpec.walkGlob]
99
- // picomatch у масиві трактує `!neg` як ОКРЕМИЙ позитивний матчер «не-neg» (some-OR логіка),
100
- // тож наївне `picomatch(['pos','!neg'])` повертає true майже на всьому. Розділяємо вручну:
101
- // позитиви join-имо через picomatch(...), негативні фільтруємо окремим isExcluded.
102
- const positives = globs.filter(g => !g.startsWith('!'))
103
- const negatives = globs.filter(g => g.startsWith('!')).map(g => g.slice(1))
104
- const isMatch = positives.length > 0 ? picomatch(positives, { dot: false }) : () => false
105
- const isExcluded = negatives.length > 0 ? picomatch(negatives, { dot: false }) : () => false
106
- return all.filter(rel => isMatch(rel) && !isExcluded(rel)).map(rel => join(root, rel))
99
+ const ig = ignore().add(globs)
100
+ return all.filter(rel => ig.ignores(rel)).map(rel => join(root, rel))
107
101
  }
108
102
  throw new Error(`target.json: files має містити single або walkGlob (отримано: ${JSON.stringify(filesSpec)})`)
109
103
  }
@@ -5,8 +5,8 @@
5
5
  *
6
6
  * Worktree-скіли (`worktree: true`) свій root-assert уже мають у worktree-блоці
7
7
  * (`worktree-notice.mjs`): корінь worktree = його toplevel. Цей модуль — для
8
- * не-worktree-кейсу (напр. `n-start-check`, що прогоняє `start` усіх воркспейсів
9
- * у місці й має стартувати з кореня монорепо).
8
+ * не-worktree-кейсу (напр. `n-taze`, що мутує `package.json` безпосередньо
9
+ * й має стартувати з кореня монорепо).
10
10
  *
11
11
  * Блок — інструкція агенту, що читає `SKILL.md`; вставляється між стабільними
12
12
  * маркерами, ре-синк ідемпотентний: наявний блок замінюється, при `false` —
@@ -7,7 +7,7 @@
7
7
  * - `requireRoot` — boolean, опційне: чи скіл вимагає запуску з кореня репо.
8
8
  * Worktree-скіли (`worktree:true`) вимагають кореня неявно (корінь worktree =
9
9
  * його toplevel), тож для них поле зайве. Явний `requireRoot:true` — для
10
- * in-place скілів, що мутують CWD без worktree-ізоляції (напр. `n-start-check`).
10
+ * in-place скілів, що мутують CWD без worktree-ізоляції (напр. `n-taze`).
11
11
  *
12
12
  * Цим хелпером користуються `auto-skills.mjs` (автоактивація), `n-cursor.js`
13
13
  * (sync + вшивання worktree/root-блоку) і check-концерн `npm-module/js/skill_meta.mjs`,
@@ -1,74 +0,0 @@
1
- ---
2
- name: n-start-check
3
- description: >-
4
- Smoke-перевірка bun-монорепо: зайти в кожен воркспейс зі `start`-скриптом, прогнати
5
- `start` і зафіксувати, чи проєкт взагалі запускається без негайного краху
6
- version: '1.0'
7
- ---
8
-
9
- # n-start-check — чи запускається кожен воркспейс
10
-
11
- ## Мета
12
-
13
- Прогнати `start`-скрипт у кожному воркспейсі bun-монорепо й зафіксувати, чи проєкт **взагалі стартує**. Це smoke-перевірка: ціль — спіймати негайний крах (синтаксична помилка, відсутній модуль, падіння на старті), а не протестувати поведінку.
14
-
15
- ## Передумови
16
-
17
- - Bun-монорепо: кореневий `package.json` має поле `workspaces`.
18
- - Залежності встановлені (`bun i`) — інакше `start` упаде на відсутньому модулі, а не на реальній проблемі.
19
- - Запуск з кореня репозиторію (де лежить кореневий `package.json` / `bun.lock`).
20
-
21
- ## Workflow
22
-
23
- ### 1. Зібрати список воркспейсів
24
-
25
- > **Не парси `package.json`/glob вручну.** Розгортання воркспейсів і класифікацію `start` несе CLI.
26
-
27
- ```bash
28
- n-cursor start-check scan
29
- ```
30
-
31
- Друкує JSON `[{ workspace, name, hasStart, startCmd, type }]` для root + усіх воркспейсів. `type` — `server` (dev-сервер/демон) чи `cli` (разова дія). Воркспейси з `hasStart:false` — `SKIP (немає start)` у звіті; решту прогоняй послідовно (крок 2).
32
-
33
- ### 2. Прогнати `start` кожного воркспейсу — по черзі
34
-
35
- > **Не запускай `perl alarm` і не інтерпретуй exit-коди вручну.** CLI запускає процес із grace-таймаутом (крос-платформно через `spawnSync`), класифікує OK/FAIL за типом і парсить лог.
36
-
37
- Для кожного воркспейсу з `hasStart:true` (**по одному, НЕ паралельно** — dev-сервери конфліктують за портами):
38
-
39
- ```bash
40
- n-cursor start-check run <workspace> # напр. demo (--grace <ms>, дефолт 12000)
41
- ```
42
-
43
- Друкує JSON:
44
-
45
- ```
46
- { workspace, type, exitCode, timedOut, status, ready, firstError, logTail, sideEffects }
47
- ```
48
-
49
- - `status: "OK"` — server дожив до кінця grace (`timedOut`) або встиг віддати рядок готовності; cli вийшов із кодом `0`.
50
- - `status: "FAIL"` — крах/ненульовий код до grace. `firstError` + `logTail` — для діагностики.
51
- - `sideEffects: { newFiles, changedTracked }` — read-only git-diff проти стану до запуску (для кроку 3).
52
-
53
- Розрізняй **помилку коду** (синтаксис, відсутній імпорт, падіння на ініціалізації) і **середовищний збій** (немає `.env`, недоступна БД/порт зайнятий) — останній не означає, що проєкт зламаний.
54
-
55
- ### 3. Відкотити побічні ефекти прогону
56
-
57
- CLI **не** мутує дерево сам (відкат — деструктивний, лишається явним). Якщо `run` повернув непорожній `sideEffects`, відкоти **лише те, що зʼявилося через прогін**, перш ніж запускати наступний воркспейс:
58
-
59
- - `sideEffects.newFiles` — нові невідстежувані файли/директорії → видали (`rm`);
60
- - `sideEffects.changedTracked` — відстежувані файли, що стали зміненими → `git checkout -- <path>`.
61
-
62
- Усе, що було брудним **до** прогону, у `sideEffects` не потрапляє (CLI рахує лише різницю) — зміни користувача не чіпай. Gitignored-артефакти (кеші, `node_modules`) у git-diff не зʼявляються.
63
-
64
- ### 4. Звіт
65
-
66
- Підсумкова таблиця: `воркспейс | статус | примітка`, з даних `scan` + `run`:
67
-
68
- - `OK` — `status:"OK"` (живий dev-сервер або вихід `0`).
69
- - `FAIL` — `status:"FAIL"`; додай `firstError` / `logTail`.
70
- - `SKIP` — `hasStart:false`.
71
-
72
- Якщо `run` повернув непорожній `sideEffects` — познач у примітці: проєкт стартує, але `start` не є чистим запуском (мутує репо). Якщо є хоч один `FAIL` — винеси його окремо з повною помилкою.
73
-
74
- Скіл лише **діагностує** запуск. Виправлення коду — поза скоупом; якщо причина FAIL очевидна, познач її у звіті, але не правь, поки про це не попросять окремо.
@@ -1,198 +0,0 @@
1
- /** @see ./docs/check.md */
2
- import { spawnSync } from 'node:child_process'
3
- import { existsSync } from 'node:fs'
4
- import { readFile } from 'node:fs/promises'
5
- import { join } from 'node:path'
6
-
7
- import { getMonorepoPackageRootDirs } from '../../../scripts/lib/workspaces.mjs'
8
-
9
- /** Маркери довгого процесу (dev-сервер/демон) у команді `start`. */
10
- const SERVER_CMD_RE = /\b(vite|next|nuxt|nodemon|serve|astro|remix|webpack-dev-server|http-server)\b|\bdev\b|--watch/
11
- /** Рядки готовності dev-сервера в логу (`\b` лише де треба — щоб «already» не матчив «ready»). */
12
- const READY_RE = /\bready\b|\blistening\b|\bstarted\b|\bcompiled\b|server running|local:/i
13
- /** Сигнатури помилок у логу. */
14
- const ERROR_RE = /(error|exception|fatal|cannot find|module not found|unhandled|panic|traceback)/i
15
- /** Скільки останніх рядків логу повертати. */
16
- const LOG_TAIL_LINES = 15
17
-
18
- /**
19
- * Класифікує `start`-команду: довгий процес (сервер) чи разова дія (CLI).
20
- * @param {string} startCmd значення `scripts.start`
21
- * @returns {'server'|'cli'} тип процесу
22
- */
23
- export function classifyStartType(startCmd) {
24
- return typeof startCmd === 'string' && SERVER_CMD_RE.test(startCmd) ? 'server' : 'cli'
25
- }
26
-
27
- /**
28
- * Зчитує `package.json` воркспейсу або повертає null.
29
- * @param {string} dir абсолютний шлях до каталогу воркспейсу
30
- * @returns {Promise<object|null>} розпарсений package.json або null
31
- */
32
- async function readPkg(dir) {
33
- const path = join(dir, 'package.json')
34
- if (!existsSync(path)) return null
35
- try {
36
- return JSON.parse(await readFile(path, 'utf8'))
37
- } catch {
38
- return null
39
- }
40
- }
41
-
42
- /**
43
- * Сканує монорепо: для кожного воркспейсу — чи є `start`, його команда і тип.
44
- * @param {string} cwd корінь репозиторію
45
- * @returns {Promise<Array<{workspace:string, name:string|null, hasStart:boolean, startCmd:string|null, type:('server'|'cli'|null)}>>} список воркспейсів
46
- */
47
- export async function scanStartWorkspaces(cwd) {
48
- const roots = await getMonorepoPackageRootDirs(cwd)
49
- const out = []
50
- for (const ws of roots) {
51
- const pkg = await readPkg(join(cwd, ws))
52
- const startCmd = pkg?.scripts?.start ?? null
53
- const hasStart = typeof startCmd === 'string' && startCmd.length > 0
54
- out.push({
55
- workspace: ws,
56
- name: pkg?.name ?? null,
57
- hasStart,
58
- startCmd: hasStart ? startCmd : null,
59
- type: hasStart ? classifyStartType(startCmd) : null
60
- })
61
- }
62
- return out
63
- }
64
-
65
- /**
66
- * Парсить лог процесу: готовність (сервер), перша помилка, хвіст.
67
- * @param {string} log обʼєднаний stdout+stderr
68
- * @returns {{ready:boolean, firstError:string|null, logTail:string}} витяг
69
- */
70
- export function parseStartLog(log = '') {
71
- const text = log
72
- const lines = text.split('\n')
73
- const firstError = lines.find(l => ERROR_RE.test(l))?.trim() ?? null
74
- const logTail = lines
75
- .filter(l => l.trim() !== '')
76
- .slice(-LOG_TAIL_LINES)
77
- .join('\n')
78
- return { ready: READY_RE.test(text), firstError, logTail }
79
- }
80
-
81
- /**
82
- * Read-only знімок `git status --porcelain` як множина рядків.
83
- * @param {string} cwd корінь репозиторію
84
- * @returns {Set<string>} рядки porcelain (статус + шлях)
85
- */
86
- function gitPorcelain(cwd) {
87
- const res = spawnSync('git', ['status', '--porcelain'], { cwd, encoding: 'utf8' })
88
- if (res.status !== 0 || typeof res.stdout !== 'string') return new Set()
89
- return new Set(res.stdout.split('\n').filter(l => l.trim() !== ''))
90
- }
91
-
92
- /**
93
- * Обчислює побічні ефекти прогону як різницю git-станів до/після.
94
- * @param {Set<string>} before знімок до
95
- * @param {Set<string>} after знімок після
96
- * @returns {{newFiles:string[], changedTracked:string[]}} нові untracked і ново-змінені tracked шляхи
97
- */
98
- function diffSideEffects(before, after) {
99
- const newFiles = []
100
- const changedTracked = []
101
- for (const line of after) {
102
- if (before.has(line)) continue
103
- const path = line.slice(3)
104
- if (line.startsWith('??')) newFiles.push(path)
105
- else changedTracked.push(path)
106
- }
107
- return { newFiles, changedTracked }
108
- }
109
-
110
- /**
111
- * Запускає `start` одного воркспейсу з grace-таймаутом і класифікує результат.
112
- * @param {string} cwd корінь репозиторію
113
- * @param {string} workspace відносний шлях воркспейсу
114
- * @param {{graceMs?:number, type?:('server'|'cli'), spawnImpl?:typeof import('node:child_process').spawnSync}} [opts] grace-період, тип (інакше з package.json), інʼєкція spawn для тестів
115
- * @returns {Promise<{workspace:string, type:string, exitCode:number|null, timedOut:boolean, status:('OK'|'FAIL'), ready:boolean, firstError:string|null, logTail:string, sideEffects:{newFiles:string[], changedTracked:string[]}}>} результат прогону
116
- */
117
- export async function runWorkspaceStart(cwd, workspace, opts = {}) {
118
- const { graceMs = 12_000, spawnImpl = spawnSync } = opts
119
- const dir = join(cwd, workspace)
120
- const pkg = await readPkg(dir)
121
- const startCmd = pkg?.scripts?.start
122
- if (typeof startCmd !== 'string' || startCmd.length === 0) {
123
- throw new Error(`У воркспейсі ${workspace} немає scripts.start`)
124
- }
125
- const type = opts.type ?? classifyStartType(startCmd)
126
-
127
- const before = gitPorcelain(cwd)
128
- const res = spawnImpl('bun', ['run', 'start'], {
129
- cwd: dir,
130
- encoding: 'utf8',
131
- timeout: graceMs,
132
- killSignal: 'SIGTERM'
133
- })
134
- const timedOut = res.error?.code === 'ETIMEDOUT' || res.signal === 'SIGTERM'
135
- const exitCode = typeof res.status === 'number' ? res.status : null
136
- const { ready, firstError, logTail } = parseStartLog(`${res.stdout ?? ''}${res.stderr ?? ''}`)
137
-
138
- // server: успіх = дожив до кінця grace (timedOut) або встиг віддати рядок готовності.
139
- // cli: успіх = чистий вихід 0 у межах grace.
140
- let status
141
- if (type === 'server') status = timedOut || ready ? 'OK' : 'FAIL'
142
- else status = exitCode === 0 ? 'OK' : 'FAIL'
143
-
144
- return {
145
- workspace,
146
- type,
147
- exitCode,
148
- timedOut,
149
- status,
150
- ready,
151
- firstError,
152
- logTail,
153
- sideEffects: diffSideEffects(before, gitPorcelain(cwd))
154
- }
155
- }
156
-
157
- const USAGE = 'Usage: n-cursor start-check <scan | run <workspace> [--grace <ms>]>'
158
-
159
- /**
160
- * CLI: `scan` друкує список воркспейсів зі `start`; `run <ws>` запускає один і
161
- * друкує класифікований результат. Обидва — JSON у stdout.
162
- * @param {string[]} args аргументи після `start-check`
163
- * @param {string} [cwd] корінь репозиторію (інʼєкція для тестів)
164
- * @returns {Promise<number>} exit code
165
- */
166
- export async function runStartCheckCli(args, cwd = process.cwd()) {
167
- const sub = args[0]
168
-
169
- if (sub === 'scan') {
170
- process.stdout.write(`${JSON.stringify(await scanStartWorkspaces(cwd))}\n`)
171
- return 0
172
- }
173
-
174
- if (sub === 'run') {
175
- const workspace = args[1] && !args[1].startsWith('--') ? args[1] : undefined
176
- if (!workspace) {
177
- console.error(USAGE)
178
- return 1
179
- }
180
- const graceAt = args.indexOf('--grace')
181
- const graceMs = graceAt === -1 ? undefined : Number(args[graceAt + 1])
182
- if (graceAt !== -1 && (!Number.isFinite(graceMs) || graceMs <= 0)) {
183
- console.error('✗ --grace очікує додатнє число (мс)')
184
- return 1
185
- }
186
- try {
187
- const result = await runWorkspaceStart(cwd, workspace, graceMs ? { graceMs } : {})
188
- process.stdout.write(`${JSON.stringify(result)}\n`)
189
- return 0
190
- } catch (error) {
191
- console.error(`✗ ${error.message}`)
192
- return 1
193
- }
194
- }
195
-
196
- console.error(USAGE)
197
- return 1
198
- }
@@ -1,42 +0,0 @@
1
- ---
2
- type: JS Module
3
- title: check.mjs
4
- resource: npm/skills/start-check/js/check.mjs
5
- docgen:
6
- crc: 1f8fb2f0
7
- score: 95
8
- ---
9
-
10
- Файл надає інструменти для класифікації типів процесу, сканування конфігурації воркспейсів, парсингу логів та запуску скриптів.
11
-
12
- ## Поведінка
13
-
14
- classifyStartType
15
- Визначає тип процесу як сервер чи CLI на основі команди.
16
-
17
- scanStartWorkspaces
18
- Сканує монорепо для отримання інформації про наявність та конфігурацію `start` скриптів у воркспейсах.
19
-
20
- parseStartLog
21
- Парсить лог процесу для визначення готовності, першої помилки та останніх рядків.
22
-
23
- runWorkspaceStart
24
- Запускає `start` для одного воркспейсу з тайм-аутом і класифікує результат.
25
-
26
- runStartCheckCli
27
- Обробляє аргументи командного рядка для сканування або запуску тестування воркспейсів.
28
-
29
- ## Публічний API
30
-
31
- - classifyStartType — визначає, чи команда `start` належить до довготривалого процесу (серверу) чи одноразової дії (CLI).
32
- - scanStartWorkspaces — переглядає монорепо, витягуючи команду `start`, її команду та тип для кожного воркспейсу.
33
- - parseStartLog — витягує з логу стадії: готовність (сервер), перша помилка або фінальний результат.
34
- - runWorkspaceStart — ініціює запуск `start` для одного воркспейсу з обмеженням часу та класифікує отриманий результат.
35
- - runStartCheckCli — CLI-команда: команда `scan` генерує список воркспейсів зі `start`; команда `run <ws>` запускає один воркспейс і виводить класифікований результат у stdout.
36
-
37
- ## Гарантії поведінки
38
-
39
- - Read-only: файл не виконує операцій запису у файлову систему.
40
- - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
41
- - За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
42
- - Не звертається до мережі.
@@ -1,11 +0,0 @@
1
- ---
2
- type: Directory Index
3
- title: npm/skills/start-check/js
4
- resource: npm/skills/start-check/js/
5
- ---
6
-
7
- # npm/skills/start-check/js
8
-
9
- | Файл | Тип |
10
- | --------------------- | --------- |
11
- | [check.mjs](check.md) | JS Module |
@@ -1 +0,0 @@
1
- { "auto": "завжди", "worktree": false, "requireRoot": true }