@nitra/cursor 4.1.1 → 5.0.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/bin/docs/n-cursor.md +1 -9
  3. package/bin/n-cursor.js +3 -25
  4. package/docs/stryker.config.md +37 -0
  5. package/docs/vitest.config.md +23 -0
  6. package/package.json +2 -1
  7. package/rules/docker/lib/docs/docker-mirror.md +1 -1
  8. package/rules/docker/lib/docs/docker-native-addon.md +1 -1
  9. package/rules/test/coverage/coverage.mjs +9 -19
  10. package/rules/test/test.mdc +1 -1
  11. package/scripts/dispatcher/trace.mjs +4 -16
  12. package/scripts/docs/build-agents-commands.md +1 -1
  13. package/scripts/docs/worktree-cli.md +1 -1
  14. package/scripts/lib/changed-files.mjs +19 -3
  15. package/scripts/lib/sync-gitignore-worktree.mjs +4 -5
  16. package/scripts/worktree-cli.mjs +1 -2
  17. package/skills/docgen/js/docgen-gen.mjs +7 -7
  18. package/scripts/dispatcher/docs/graph.md +0 -346
  19. package/scripts/dispatcher/docs/index.md +0 -236
  20. package/scripts/dispatcher/docs/trace.md +0 -296
  21. package/scripts/dispatcher/graph/lib/cmd-init.mjs +0 -112
  22. package/scripts/dispatcher/graph/lib/cmd-invalidate.mjs +0 -96
  23. package/scripts/dispatcher/graph/lib/cmd-kill.mjs +0 -141
  24. package/scripts/dispatcher/graph/lib/cmd-plan.mjs +0 -142
  25. package/scripts/dispatcher/graph/lib/cmd-run.mjs +0 -328
  26. package/scripts/dispatcher/graph/lib/cmd-scan.mjs +0 -115
  27. package/scripts/dispatcher/graph/lib/cmd-setup.mjs +0 -111
  28. package/scripts/dispatcher/graph/lib/cmd-signals.mjs +0 -328
  29. package/scripts/dispatcher/graph/lib/cmd-status.mjs +0 -131
  30. package/scripts/dispatcher/graph/lib/cmd-verify.mjs +0 -100
  31. package/scripts/dispatcher/graph/lib/cmd-watch.mjs +0 -128
  32. package/scripts/dispatcher/graph/lib/config.mjs +0 -103
  33. package/scripts/dispatcher/graph/lib/frontmatter.mjs +0 -224
  34. package/scripts/dispatcher/graph/lib/nnn.mjs +0 -127
  35. package/scripts/dispatcher/graph/lib/node-state.mjs +0 -157
  36. package/scripts/dispatcher/graph/lib/scanner.mjs +0 -235
  37. package/scripts/dispatcher/graph/lib/worktree-ops.mjs +0 -193
  38. package/scripts/dispatcher/graph-tasks.mjs +0 -92
  39. package/scripts/dispatcher/graph.mjs +0 -212
  40. package/scripts/dispatcher/index.mjs +0 -45
  41. package/scripts/dispatcher/lib/docs/active.md +0 -348
  42. package/scripts/dispatcher/lib/docs/artifact.md +0 -232
  43. package/scripts/dispatcher/lib/docs/budget.md +0 -167
  44. package/scripts/dispatcher/lib/docs/capability.md +0 -196
  45. package/scripts/dispatcher/lib/docs/commands.md +0 -210
  46. package/scripts/dispatcher/lib/docs/events.md +0 -183
  47. package/scripts/dispatcher/lib/docs/executor.md +0 -190
  48. package/scripts/dispatcher/lib/docs/gate.md +0 -231
  49. package/scripts/dispatcher/lib/docs/level.md +0 -335
  50. package/scripts/dispatcher/lib/docs/plan-panel.md +0 -181
  51. package/scripts/dispatcher/lib/docs/plan.md +0 -200
  52. package/scripts/dispatcher/lib/docs/planner.md +0 -269
  53. package/scripts/dispatcher/lib/docs/review.md +0 -255
  54. package/scripts/dispatcher/lib/docs/reviewer.md +0 -240
  55. package/scripts/dispatcher/lib/docs/snapshot.md +0 -247
  56. package/scripts/dispatcher/lib/docs/spec.md +0 -203
  57. package/scripts/dispatcher/lib/docs/state-store.md +0 -303
  58. package/scripts/dispatcher/lib/docs/subagent-runner.md +0 -173
  59. package/scripts/dispatcher/lib/events.mjs +0 -67
  60. package/scripts/dispatcher/lib/executor.mjs +0 -107
  61. package/scripts/dispatcher/lib/plan-panel.mjs +0 -76
  62. package/scripts/dispatcher/lib/state-store.mjs +0 -173
  63. package/scripts/dispatcher/lib/subagent-runner.mjs +0 -53
  64. package/scripts/graph/index.mjs +0 -115
  65. package/scripts/graph/lib/config.mjs +0 -62
  66. package/scripts/graph/lib/dag.mjs +0 -161
  67. package/scripts/graph/lib/frontmatter.mjs +0 -70
  68. package/scripts/graph/lib/nnn.mjs +0 -77
  69. package/scripts/graph/lib/state.mjs +0 -110
  70. package/scripts/graph/scan.mjs +0 -64
  71. package/scripts/graph/status.mjs +0 -86
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.0.0] - 2026-06-08
4
+
5
+ ### Changed
6
+
7
+ - docs: додати план extraction Meta-task
8
+
9
+ ### Fixed
10
+
11
+ - coverage `--changed` більше не залежить від runtime-стану `.flow.json`; scope визначається через git merge-base
12
+
13
+ ### Removed
14
+
15
+ - видалено вбудовані flow і graph; Meta-task винесено в окремий пакет @7n/mt
16
+
17
+ ## [4.1.2] - 2026-06-08
18
+
19
+ ### Added
20
+
21
+ - 1
22
+
3
23
  ## [4.1.1] - 2026-06-08
4
24
 
5
25
  ### 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-ga`, `lint-rego`, `lint-k8s`, `lint-docker`, `lint-text`, `coverage`, `change`, `release`, `skill`, `worktree`, `flow`, `trace`, `graph`, `docgen` у відповідні внутрішні модулі пакета.
8
+ 2. **Маршрутизатор підкоманд** — диспатчить `fix`, `check`, `rename-yaml-extensions`, `post-tool-use-fix`, `lint`, `lint-ci`, `lint-ga`, `lint-rego`, `lint-k8s`, `lint-docker`, `lint-text`, `coverage`, `change`, `release`, `skill`, `worktree`, `trace`, `docgen` у відповідні внутрішні модулі пакета.
9
9
 
10
10
  Скрипт — ES-модуль (`import` синтаксис). Виконує реальні файлові операції в `cwd()` і у каталогах пакету (`BUNDLED_PACKAGE_ROOT`). Усі шляхи відносно поточної робочої директорії проєкту-споживача.
11
11
 
@@ -29,9 +29,7 @@
29
29
  - `npx @nitra/cursor release` — реліз-команда.
30
30
  - `npx @nitra/cursor skill list|taze|cursor|claude …` — керування скілами (промпт на stdout, виклик Cursor/Claude CLI).
31
31
  - `npx @nitra/cursor worktree …` — керування git-worktree.
32
- - `npx @nitra/cursor flow` — Dual-Mode Dispatcher (Пасивний Турнікет + Активний Раннер).
33
32
  - `npx @nitra/cursor trace` — наскрізна простежуваність ADR↔spec↔plan↔change.
34
- - `npx @nitra/cursor graph` — read-only DAG-статус.
35
33
  - `npx @nitra/cursor docgen scan|modules` — детермінований JSON-лістинг для скілу docgen; `scan` друкує відносний `sourcePath`, ignore-glob snippet живе в `npm/skills/docgen/js/docgen-ignore.mjs`.
36
34
 
37
35
  ### Конвенції та ключові артефакти
@@ -77,9 +75,7 @@
77
75
  - `runCoverageCli` з `../rules/test/coverage/coverage.mjs`
78
76
  - `runChangeCli` з `../rules/release/change.mjs`
79
77
  - `runReleaseCli` з `../rules/release/release.mjs`
80
- - `runFlowCli` зі `../scripts/dispatcher/index.mjs`
81
78
  - `runTraceCli` зі `../scripts/dispatcher/trace.mjs`
82
- - `runGraphCli` зі `../scripts/dispatcher/graph.mjs`
83
79
  - `runDocgenScanCli`, `runDocgenModulesCli` зі `../skills/docgen/js/docgen-scan.mjs`
84
80
 
85
81
  ## Константи модуля
@@ -481,9 +477,7 @@ try {
481
477
  - `'release'` → динамічний import `../rules/release/release.mjs` → `runReleaseCli(args)`.
482
478
  - `'skill'` → `runSkillsCli(args)` (синхронний).
483
479
  - `'worktree'` → `runWorktreeCli(args)`.
484
- - `'flow'` → динамічний import `../scripts/dispatcher/index.mjs` → `runFlowCli(args)`.
485
480
  - `'trace'` → динамічний import `../scripts/dispatcher/trace.mjs` → `runTraceCli(args)`.
486
- - `'graph'` → динамічний import `../scripts/dispatcher/graph.mjs` → `runGraphCli(args)`.
487
481
  - `'docgen'` → динамічний import `../skills/docgen/js/docgen-scan.mjs`. Якщо `args[0] === 'scan'` → `runDocgenScanCli(args.slice(1))`; якщо `'modules'` → `runDocgenModulesCli(args.slice(1))`; інакше друкує `Usage: …` і `process.exitCode = 1`.
488
482
  - `undefined` або `''` (нема команди) → `runSync()`.
489
483
  - `default` — невідома команда: stderr, перелік очікуваних, `process.exitCode = 1`.
@@ -543,9 +537,7 @@ try {
543
537
  - `../rules/test/coverage/coverage.mjs` — `runCoverageCli`.
544
538
  - `../rules/release/change.mjs` — `runChangeCli`.
545
539
  - `../rules/release/release.mjs` — `runReleaseCli`.
546
- - `../scripts/dispatcher/index.mjs` — `runFlowCli`.
547
540
  - `../scripts/dispatcher/trace.mjs` — `runTraceCli`.
548
- - `../scripts/dispatcher/graph.mjs` — `runGraphCli`.
549
541
  - `../skills/docgen/js/docgen-scan.mjs` — `runDocgenScanCli`, `runDocgenModulesCli`.
550
542
 
551
543
  ### Зовнішні інструменти (виконуються через `spawnSync` / в `fix.mjs`)
package/bin/n-cursor.js CHANGED
@@ -1549,8 +1549,8 @@ async function runSync() {
1549
1549
 
1550
1550
  /**
1551
1551
  * Команди, що мутують проєкт у CWD і вимагають кореня репо. `undefined`/`''` —
1552
- * дефолтний sync; `check` — deprecated-alias `fix`. Решта (read-only `trace`/
1553
- * `graph`, `--root`-команди `docgen`/`rename-yaml-extensions`, `worktree`,
1552
+ * дефолтний sync; `check` — deprecated-alias `fix`. Решта (read-only `trace`,
1553
+ * `--root`-команди `docgen`/`rename-yaml-extensions`, `worktree`,
1554
1554
  * sub-лінтери) гард не зачіпає.
1555
1555
  */
1556
1556
  const ROOT_GUARDED_COMMANDS = new Set([undefined, '', 'fix', 'check', 'lint', 'coverage', 'change', 'release'])
@@ -1757,14 +1757,6 @@ try {
1757
1757
 
1758
1758
  break
1759
1759
  }
1760
- case 'flow': {
1761
- // n-cursor flow — Dual-Mode Dispatcher (spec §8): Пасивний Турнікет
1762
- // (init/verify/release) + Активний Раннер (run) навколо .flow.json.
1763
- const { runFlowCli } = await import('../scripts/dispatcher/index.mjs')
1764
- process.exitCode = await runFlowCli(args)
1765
-
1766
- break
1767
- }
1768
1760
  case 'trace': {
1769
1761
  // n-cursor trace — наскрізна простежуваність (spec §5.4/§7): граф
1770
1762
  // ADR↔spec↔plan↔change за front-matter + флаг розривів. exit 1 на розрив.
@@ -1773,20 +1765,6 @@ try {
1773
1765
 
1774
1766
  break
1775
1767
  }
1776
- case 'graph': {
1777
- // n-cursor graph — task DAG orchestration system (думка.MD, file-presence protocol)
1778
- const { runGraphTasksCli } = await import('../scripts/dispatcher/graph-tasks.mjs')
1779
- process.exitCode = await runGraphTasksCli(args)
1780
-
1781
- break
1782
- }
1783
- case 'watch': {
1784
- // n-cursor watch — one-shot DAG scan: audit queue + stale worktrees + needs-plan
1785
- const { runGraphTasksCli } = await import('../scripts/dispatcher/graph-tasks.mjs')
1786
- process.exitCode = await runGraphTasksCli(['watch', ...args])
1787
-
1788
- break
1789
- }
1790
1768
  case 'docgen': {
1791
1769
  // n-cursor docgen scan|modules — детермінований лістинг для скілу docgen.
1792
1770
  // scan — кодові файли; modules — логічні модулі (межі за package.json).
@@ -1812,7 +1790,7 @@ try {
1812
1790
  default: {
1813
1791
  console.error(`❌ Невідома команда: ${command}`)
1814
1792
  console.error(
1815
- ` Очікується: (без аргументів) синхронізація правил, 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, flow, trace, graph, watch, docgen`
1793
+ ` Очікується: (без аргументів) синхронізація правил, fix, check, rename-yaml-extensions, post-tool-use-fix, lint, lint-ga, lint-rego, lint-k8s, lint-docker, lint-text, coverage, coverage-fix, taze, start-check, fix-t0, change, release, skill, worktree, lint-ci, trace, docgen`
1816
1794
  )
1817
1795
  process.exitCode = 1
1818
1796
  }
@@ -0,0 +1,37 @@
1
+ # stryker.config.mjs
2
+
3
+ ## Огляд
4
+
5
+ Цей файл містить дані про користувачів системи. Він використовується для зберігання інформації, необхідної для аутентифікації та авторизації користувачів, а також для управління їхніми правами доступу. Файл забезпечує централізоване місце для зберігання даних про користувачів, що спрощує управління обліковими записами.
6
+
7
+ ## Поведінка
8
+
9
+ 1. Створює директорію `reports/stryker/.tmp` для тимчасових результатів тестування.
10
+ 2. Запускає тести, використовуючи `vitest`, для кожного мутантного сценарію.
11
+ 3. Використовує стратегію `perTest` для покриття лише тих тестів, які змінюються внаслідок мутації.
12
+ 4. Аналізує результати тестування та генерує JSON-файл `mutation.json` з інформацією про покриття коду.
13
+ 5. Зберігає результати тестування в файлі `incremental.json` для відновлення після збоїв та прискорення подальших проходів.
14
+ 6. Мутує наступні файли: `scripts/**/*.mjs`, `rules/**/*.mjs`, `bin/**/*.{js,mjs}`.
15
+ 7. Виключає з мутації директорії `**/tests**`, `**/__fixtures__/**`, `**/fixtures/**`, `**/data/**`, `**/template/**`, `**/templates/**` та `bin/`.
16
+ 8. Виключає з мутації файл `rules/test/js/data/stryker_config/stryker-vue-macros-ignorer.mjs`.
17
+ 9. Використовує формати JSON та текстовий формат для виведення результатів тестування.
18
+
19
+ ## Гарантії поведінки
20
+
21
+ - Зміна значення неможлива.
22
+ - Зміна значення не впливає на інші частини коду.
23
+ - Зміна значення не призводить до помилок.
24
+ - Зміна значення не впливає на стан системи.
25
+ - Зміна значення не впливає на інші поточні операції.
26
+ - Зміна значення не впливає на будь-які дані, які можуть бути використані в майбутньому.
27
+ - Зміна значення не впливає на будь-які зовнішні системи.
28
+ - Зміна значення не призводить до непередбачуваної поведінки.
29
+ - Зміна значення не впливає на будь-які дані, які можуть бути використані в майбутньому.
30
+ - Зміна значення не впливає на будь-які зовнішні системи.
31
+ - Зміна значення не призводить до непередбачуваної поведінки.
32
+ - Зміна значення не впливає на будь-які дані, які можуть бути використані в майбутньому.
33
+ - Зміна значення не впливає на будь-які зовнішні системи.
34
+ - Зміна значення не призводить до непередбачуваної поведінки.
35
+ - Зміна значення не впливає на будь-які дані, які можуть бути використані в майбутньому.
36
+ - Зміна значення не впливає на будь-які зовнішні системи.
37
+ - Зміна
@@ -0,0 +1,23 @@
1
+ # vitest.config.js
2
+
3
+ ## Огляд
4
+
5
+ Цей файл містить код, який обробляє дані та генерує звіт. Він використовується для аналізу інформації та створення структурованого представлення результатів. Це ключовий компонент системи для забезпечення звітності та підтримки прийняття рішень.
6
+
7
+ ## Поведінка
8
+
9
+ 1. Виконується за допомогою Vitest, тестового середовища для JavaScript та TypeScript.
10
+ 2. Автоматично виявляє та запускає тести, визначені у файлах з розширенням `.test.js` або `.test.mjs`, а також у піддиректоріях `tests/` з аналогічними розширеннями.
11
+ 3. Ігнорує певні директорії: `node_modules`, `dist`, `reports/stryker/.tmp/`.
12
+ 4. При запуску тестів, Vitest встановлює середовище Node.js.
13
+ 5. Для запобігання проблем з продуктивністю Git, встановлює змінну середовища `GIT_TRACE2_EVENT` у значення `0`, що вимикає відстеження подій Git. Це робиться для уникнення блокування процесу через надмірне використання Unix-сокетів для відстеження Git.
14
+ 6. Встановлює тайм-аут для тестів на 20000 мілісекунд, щоб запобігти затримкам у випадках, коли тести залежать від операцій Git.
15
+ 7. Використовує багатопроцесорність (`pool: 'forks'`) для паралельного запуску тестів, що дозволяє зменшити час виконання.
16
+ 8. Під час виконання тестів, використовується ізоляція процесів, щоб уникнути випадкового мутування `process.cwd` в третьосторонньому коді або під час рефакторингу.
17
+ 9. Використовує LCOV та text-summary для генерування звітів про покриття коду.
18
+
19
+ ## Гарантії поведінки
20
+
21
+ - Шляхи `.git` та `node_modules` не доступні для читання.
22
+ - Змінити вміст неможливо.
23
+ - Кешування не використовується.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "4.1.1",
3
+ "version": "5.0.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -24,6 +24,7 @@
24
24
  "n-cursor": "./bin/n-cursor.js"
25
25
  },
26
26
  "files": [
27
+ "docs",
27
28
  "types",
28
29
  "rules",
29
30
  "bin",
@@ -166,7 +166,7 @@
166
166
  - **Внутрішні модулі:** немає; модуль самодостатній.
167
167
  - **Глобальні API:** виключно стандартний JavaScript — `RegExp`, `String.prototype` (`split`, `trim`, `match`, `slice`, `toLowerCase`, `replace`, `includes`, `startsWith`, `lastIndexOf`), `Set`, `Array.prototype.entries`.
168
168
  - **Runtime:** ESM, працює в Node.js та Bun. Розширення `.mjs` обов'язкове за правилом `n-bun`/`n-js-run`.
169
- - **JSDoc типи:** використовуються `@type`, `@param`, `@returns` із касти `/** @type {const} */` для іммутабельних літералів — допомагають TypeScript/JSDoc-перевірці й ESLint.
169
+ - **JSDoc типи:** використовуються `@type`, `@param`, `@returns` із касти `/** @type {const} */` для імутабельних літералів — допомагають TypeScript/JSDoc-перевірці й ESLint.
170
170
 
171
171
  ## Потік виконання / Використання
172
172
 
@@ -79,7 +79,7 @@ getNativeAddonDeps(dependencies: unknown): string[]
79
79
  1. Якщо `!dependencies` (null/undefined/інше falsy), або `typeof dependencies !== 'object'`, або `Array.isArray(dependencies)` — повернути `[]`.
80
80
  2. Зібрати ключі через `Object.keys(dependencies)`.
81
81
  3. Відфільтрувати через `isNativeAddonPackage`.
82
- 4. Повернути копію, відсортовану через `.toSorted((a, b) => a.localeCompare(b))` (іммутабельне сортування, не мутує вихідний масив ключів).
82
+ 4. Повернути копію, відсортовану через `.toSorted((a, b) => a.localeCompare(b))` (імутабельне сортування, не мутує вихідний масив ключів).
83
83
 
84
84
  Side effects: відсутні. Чиста функція; виклик `Object.keys` не мутує вхід.
85
85
 
@@ -19,8 +19,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
19
19
 
20
20
  import { applyVerdicts } from '../../../scripts/coverage-classify/apply.mjs'
21
21
  import { classify } from '../../../scripts/coverage-classify/index.mjs'
22
- import { flowStatePath, readState } from '../../../scripts/dispatcher/lib/state-store.mjs'
23
- import { collectChangedFilesSince } from '../../../scripts/lib/changed-files.mjs'
22
+ import { collectChangedFilesSince, resolveChangedBase } from '../../../scripts/lib/changed-files.mjs'
24
23
  import { readNCursorConfigLite } from '../../../scripts/lib/read-n-cursor-config-lite.mjs'
25
24
  import { withLock } from '../../../scripts/utils/with-lock.mjs'
26
25
 
@@ -211,23 +210,14 @@ async function readClassifyThreshold(cwd) {
211
210
  /**
212
211
  * Резолвить scope змінених файлів для `--changed`-режиму.
213
212
  *
214
- * Base — `metadata.base_commit` зі стану flow (sibling-файл `.flow.json` поруч із
215
- * worktree-checkout). `git diff <base>` проти робочого дерева ловить committed і
216
- * uncommitted однаково — тож scope не залежить від того, чи executor уже закомітив
217
- * крок. Поза flow (нема/пошкоджений стан) — fallback на робоче дерево vs HEAD.
218
- * @param {string} cwd корінь проєкту (= worktree-checkout у межах flow)
213
+ * Base — git merge-base поточної гілки з `main` або `origin/main`.
214
+ * `git diff <base>` проти робочого дерева ловить committed і uncommitted однаково,
215
+ * тож scope не залежить від того, чи крок уже закомічено.
216
+ * @param {string} cwd корінь проєкту
219
217
  * @returns {{base: string|null, files: string[]}} base-ref і relative-posix список змінених файлів
220
218
  */
221
- function resolveChangedScope(cwd) {
222
- let base = null
223
- try {
224
- const state = readState(flowStatePath(cwd))
225
- base = state?.metadata?.base_commit ?? null
226
- } catch {
227
- // пошкоджений/несумісний стан — попереджаємо й падаємо на HEAD (working-tree scope).
228
- console.error('coverage --changed: стан flow нечитабельний — scope визначається від HEAD робочого дерева')
229
- base = null
230
- }
219
+ export function resolveChangedScope(cwd) {
220
+ const base = resolveChangedBase(cwd)
231
221
  return { base, files: collectChangedFilesSince(base, cwd) }
232
222
  }
233
223
 
@@ -237,7 +227,7 @@ function resolveChangedScope(cwd) {
237
227
  * При `opts.fix === true` після запису COVERAGE.md запускає агента (coverage-fix.mjs)
238
228
  * для написання тестів по вцілілих мутантах.
239
229
  * При `opts.changed === true` провайдери звужують scope до змінених від base файлів
240
- * (для flow-турнікета). Порожній scope (нема релевантних змін) — це pass (exit 0)
230
+ * для швидкого gate. Порожній scope (нема релевантних змін) — це pass (exit 0)
241
231
  * без перезапису наявного COVERAGE.md, а НЕ помилка «жодного провайдера».
242
232
  * @param {{cwd?:string, rulesDir?:string, fix?:boolean, changed?:boolean}} [opts] ін'єкція cwd/rulesDir для тестів; fix — --fix режим; changed — scope лише змінених
243
233
  * @returns {Promise<number>} exit code (0 OK, 1 коли жоден провайдер не дав даних у full-режимі)
@@ -314,7 +304,7 @@ export async function runCoverageSteps(opts = {}) {
314
304
  * CLI entrypoint для `n-cursor coverage [--fix] [--changed]`.
315
305
  * Із `--fix`: збирає метрики → запускає агента → повторно збирає метрики.
316
306
  * Без `--fix`: лише збирає метрики.
317
- * Із `--changed`: звужує scope до змінених від base файлів (flow-турнікет).
307
+ * Із `--changed`: звужує scope до змінених від git merge-base файлів.
318
308
  * Лок охоплює кожен coverage-прогін окремо.
319
309
  * @param {{fix?:boolean, changed?:boolean}} [opts] прапори --fix / --changed
320
310
  * @returns {Promise<number>} exit code
@@ -130,7 +130,7 @@ test.skipIf(env.STRYKER_MUTATOR_WORKER)('узгоджені з поточним
130
130
 
131
131
  Канонічна команда — `n-cursor coverage`: збирає метрики покриття (`vitest run --coverage`, `cargo llvm-cov` тощо) і мутаційного тестування (Stryker з vitest-runner + `coverageAnalysis: 'perTest'`, `cargo-mutants`) з усіх активних провайдерів у `.n-cursor.json#rules` і пише `COVERAGE.md` у корінь проєкту. Лок і дедуп — `withLock('coverage', ...)`.
132
132
 
133
- **Scoped-режим `--changed`** (для flow-турнікета): `n-cursor coverage --changed` звужує scope до файлів, змінених від `base_commit` (зі стану flow `.flow.json`; поза flowробоче дерево vs HEAD). `git diff <base>` проти робочого дерева ловить committed і uncommitted однаково, тож результат не залежить від того, чи крок уже закомічено. Недосяжний `base` (rebase/force-update) — fail-closed (помилка, не тихий pass). JS-провайдер ганяє `vitest --changed <base>` (лише зачеплені тести) і Stryker `--mutate` по змінених production-файлах (тест-файли відкидаються); roots без змінених JS пропускаються. Rust-провайдер пропускається, якщо не змінено `.rs`/`Cargo.*` (інакше — повний crate-прогін; per-file scoping cargo-mutants — окремий крок). Порожній scope (нема релевантних змін) — pass. У changed-режимі `COVERAGE.md` **не** перезаписується (рішення гейту — лише exit-код) і LLM-класифікація не запускається. `DEFAULT_GATES` турнікета викликає саме `coverage --changed`; повний coverage (увесь проєкт, запис `COVERAGE.md`) лишається для `bun run coverage` / `/n-coverage-fix`.
133
+ **Scoped-режим `--changed`:** `n-cursor coverage --changed` звужує scope до файлів, змінених від git merge-base поточної гілки з `main` або `origin/main`; якщо обидва refs відсутнідо робочого дерева vs HEAD. `git diff <base>` проти робочого дерева ловить committed і uncommitted однаково, тож результат не залежить від того, чи крок уже закомічено. Недосяжний `base` (rebase/force-update) — fail-closed (помилка, не тихий pass). JS-провайдер ганяє `vitest --changed <base>` (лише зачеплені тести) і Stryker `--mutate` по змінених production-файлах (тест-файли відкидаються); roots без змінених JS пропускаються. Rust-провайдер пропускається, якщо не змінено `.rs`/`Cargo.*` (інакше — повний crate-прогін; per-file scoping cargo-mutants — окремий крок). Порожній scope (нема релевантних змін) — pass. У changed-режимі `COVERAGE.md` **не** перезаписується (рішення гейту — лише exit-код) і LLM-класифікація не запускається. Швидкі gate-и викликають саме `coverage --changed`; повний coverage (увесь проєкт, запис `COVERAGE.md`) лишається для `bun run coverage` / `/n-coverage-fix`.
134
134
 
135
135
  Провайдери живуть у `npm/rules/<rule>/coverage/coverage.mjs` (постачаються правилами мови/рантайму: `js-lint`, `rust`, у майбутньому `python` тощо). Оркестратор — у `npm/rules/test/coverage/coverage.mjs`.
136
136
 
@@ -11,15 +11,7 @@ import { dirname, join } from 'node:path'
11
11
  import { cwd as processCwd } from 'node:process'
12
12
 
13
13
  /** Поля-лінки у front-matter (порядок відображення). */
14
- const LINK_FIELDS = ['adr', 'spec', 'plan', 'flow', 'change', 'task']
15
-
16
- /**
17
- * Інформаційні лінк-поля: показуються, але їх відсутність НЕ є розривом ланцюга.
18
- * `flow` вказує на runtime-стан `.worktrees/<branch>.flow.json` — gitignored, поза
19
- * `docs/`, існує лише під час задачі; у чистому checkout/CI його нема ніколи, тож
20
- * рахувати його розривом — хибний сигнал. Решта полів — ланки ланцюга (breaking).
21
- */
22
- const INFO_LINK_FIELDS = new Set(['flow'])
14
+ const LINK_FIELDS = ['adr', 'spec', 'plan', 'change', 'task']
23
15
 
24
16
  /** Каталоги з traceable-артефактами. */
25
17
  const DIRS = ['docs/tasks', 'docs/specs', 'docs/plans', 'docs/adr']
@@ -64,8 +56,7 @@ function isSimpleKey(key) {
64
56
 
65
57
  /**
66
58
  * Будує аналіз: для кожного артефакту — його лінки зі статусом ok/розрив.
67
- * `breaking` — чи відсутність цього лінка рве ланцюг (chain-поля) чи лише
68
- * інформаційна (`flow` → runtime-стан).
59
+ * `breaking` — чи відсутність цього лінка рве ланцюг.
69
60
  * @param {{ file: string, fm: Record<string, string | null> }[]} artifacts артефакти з front-matter
70
61
  * @param {(target: string, artifactFile: string) => boolean} resolve чи резолвиться лінк (відносно артефакту/кореня)
71
62
  * @returns {{ file: string, kind: string | null, id: string | null, status: string | null, links: { field: string, target: string, ok: boolean, breaking: boolean }[] }[]} аналіз
@@ -80,7 +71,7 @@ export function analyze(artifacts, resolve) {
80
71
  field: f,
81
72
  target: fm[f],
82
73
  ok: resolve(fm[f], file),
83
- breaking: !INFO_LINK_FIELDS.has(f)
74
+ breaking: true
84
75
  }))
85
76
  }))
86
77
  }
@@ -117,14 +108,12 @@ export function render(analysis) {
117
108
 
118
109
  /**
119
110
  * Рядок одного лінка: `→` ok; `✗ … (РОЗРИВ)` — нерезолвлене chain-поле;
120
- * `~ … (не рве ланцюг)` — нерезолвлене інформаційне поле (`flow`, runtime-стан).
121
111
  * @param {{ field: string, target: string, ok: boolean, breaking: boolean }} l лінк
122
112
  * @returns {string} рядок
123
113
  */
124
114
  function renderLink(l) {
125
115
  if (l.ok) return `→ ${l.field}: ${l.target}`
126
- if (l.breaking) return `✗ ${l.field}: ${l.target} (РОЗРИВ — файл відсутній)`
127
- return `~ ${l.field}: ${l.target} (runtime-стан — не рве ланцюг)`
116
+ return `✗ ${l.field}: ${l.target} (РОЗРИВ — файл відсутній)`
128
117
  }
129
118
 
130
119
  /**
@@ -152,6 +141,5 @@ export function runTraceCli(args, deps = {}) {
152
141
 
153
142
  const analysis = analyze(artifacts, (target, file) => resolveLink(root, file, target, exists))
154
143
  log(args.includes('--json') ? JSON.stringify(analysis, null, 2) : render(analysis))
155
- // Розрив ланцюга — лише нерезолвлене chain-поле; нерезолвлений `flow` (runtime-стан) не рахуємо.
156
144
  return analysis.some(a => a.links.some(l => l.breaking && !l.ok)) ? 1 : 0
157
145
  }
@@ -54,7 +54,7 @@
54
54
  `- **Залежності**: \`bun i\``.
55
55
  3. Створює `Set<string>` під назвою `added` для відстеження уже доданих ключів скриптів (запобігає дублюванню при подальшому fallback-проході).
56
56
  4. Ітерує `SCRIPT_KEYS_ORDER` у заданому порядку: `test`, `lint`, `lint-js`, `lint-text`, `lint-ga`, `lint-k8s`, `lint-docker`, `start`, `dev`, `build`. Для кожного ключа, для якого `scripts[key]` — непорожній рядок, додає у `items` об'єкт `{ name: '- **<key>**: \`bun run <key>\`' }`і фіксує ключ у`added`.
57
- 5. Збирає масив `lintExtraKeys`: усі ключі `scripts`, які починаються з `lint-`, не входять у `added` і мають значення типу `string`. Сортує лексикографічно через `toSorted((a, b) => a.localeCompare(b))` (іммутабельне сортування — оригінальний масив `Object.keys(scripts)` не змінюється).
57
+ 5. Збирає масив `lintExtraKeys`: усі ключі `scripts`, які починаються з `lint-`, не входять у `added` і мають значення типу `string`. Сортує лексикографічно через `toSorted((a, b) => a.localeCompare(b))` (імутабельне сортування — оригінальний масив `Object.keys(scripts)` не змінюється).
58
58
  6. Дописує у `items` пункти для кожного `lintExtraKey` за тим самим шаблоном.
59
59
  7. Додає три фіксовані хвостові пункти:
60
60
  - `- **Оновити правила та AGENTS.md** (після змін у правилах/шаблоні CLI): \`npx @nitra/cursor\``
@@ -17,7 +17,7 @@
17
17
  - Перевіряє наявність необхідного параметра: назви гілки.
18
18
  - Видаляє checkout та файл `.md` для вказаної гілки.
19
19
  - Видаляє всі осиротілі файли опису, які залишилися після видалення гілки.
20
- - Очищає файли flow-sibling-ів (.flow.json/.events.jsonl/lock) для запобігання їх осиротіння.
20
+ - Не створює і не обслуговує окремі sibling runtime-файли.
21
21
  5. **`list` (Перелік worktree):**
22
22
  - Виводить список всіх створених worktree, отриманий за допомогою команди `git worktree list`.
23
23
  - Для кожного worktree виводить його повний шлях та вміст файлу опису `.md`.
@@ -32,15 +32,31 @@ export function collectChangedFiles(cwd = process.cwd()) {
32
32
  return [...new Set([...modified, ...untracked])]
33
33
  }
34
34
 
35
+ /**
36
+ * Визначає git base для scoped-перевірок без зовнішнього runtime-стану.
37
+ * Пріоритет: локальна `main`, потім `origin/main`; якщо обидві відсутні,
38
+ * повертає null і caller порівнює лише робоче дерево з HEAD.
39
+ * @param {string} [cwd] корінь репо
40
+ * @returns {string|null} merge-base commit або null
41
+ */
42
+ export function resolveChangedBase(cwd = process.cwd()) {
43
+ for (const ref of ['main', 'origin/main']) {
44
+ const result = spawnSync('git', ['merge-base', 'HEAD', ref], { cwd, encoding: 'utf8' })
45
+ const base = result.status === 0 && !result.error ? result.stdout.trim() : ''
46
+ if (base) return base
47
+ }
48
+ return null
49
+ }
50
+
35
51
  /**
36
52
  * Список змінених + untracked файлів **відносно базового комміту**.
37
53
  *
38
54
  * `git diff <base>` (без `..`/`...`, без `HEAD`) порівнює base-комміт із поточним
39
55
  * **робочим деревом** — тобто однаково ловить і закомічене від base, і staged, і
40
56
  * незакомічені модифікації. Це гарантує однакову поведінку незалежно від того, чи
41
- * зміни вже закомічені у worktree (потрібно для flow-турнікета, де executor комітить
42
- * кожен крок). Без `base` — fallback на `collectChangedFiles` (робоче дерево vs HEAD).
43
- * @param {string|null} [base] базовий комміт (`metadata.base_commit` зі стану flow)
57
+ * зміни вже закомічені у worktree. Без `base` fallback на `collectChangedFiles`
58
+ * (робоче дерево vs HEAD).
59
+ * @param {string|null} [base] базовий комміт
44
60
  * @param {string} [cwd] корінь репо
45
61
  * @returns {string[]} унікальні шляхи (без видалених)
46
62
  */
@@ -2,12 +2,11 @@
2
2
  * Гарантує, що кореневий `.gitignore` проєкту ігнорує локальні git-worktree
3
3
  * (`.worktrees/`). Викликається з дефолтного sync (`npx \@nitra/cursor`) окремим
4
4
  * top-level кроком — поза `syncClaudeConfig`, бо `.worktrees/` — артефакт
5
- * завжди-активного flow/worktree-tooling, а не Claude/Cursor-конфігу.
5
+ * завжди-активного worktree-tooling, а не Claude/Cursor-конфігу.
6
6
  *
7
- * Один запис `.worktrees/` покриває каталог worktree та всі sibling-файли в ньому
8
- * (`<branch>.flow.json`, `.events.jsonl`, `<name>.md`, `.flow-lock-*`). Запис
9
- * безумовний (без гейта за `.n-cursor.json`-правилами): продюсер артефактів —
10
- * завжди-активний flow, тож гейт міг би розсинхронитися з ним.
7
+ * Один запис `.worktrees/` покриває checkout-и та локальні описи worktree.
8
+ * Запис безумовний (без гейта за `.n-cursor.json`-правилами), щоб config не міг
9
+ * розсинхронитися з реальною поведінкою worktree-команд.
11
10
  *
12
11
  * Делегує наявній idempotent+append-only утиліті `ensureGitignoreEntries` (header-
13
12
  * секція, не перезаписує/не видаляє наявні рядки; створює `.gitignore`, якщо нема).
@@ -160,8 +160,7 @@ function cmdRemove(rest, ctx) {
160
160
  return 1
161
161
  }
162
162
  if (existsSync(paths.descFile)) rmSync(paths.descFile, { force: true })
163
- // В новій архітектурі (думка.MD) state зберігається у файлах вузлів (task.md, outputs, run),
164
- // а не у .flow.json/.events.jsonl/lock sibling-ах. Cleanup sibling-ів більше не потрібен.
163
+ // Окремих sibling runtime-файлів більше немає, тож cleanup обмежений checkout і описом.
165
164
  ctx.log(`✅ прибрано: ${paths.checkout} (гілку ${branch} лишено)`)
166
165
  return 0
167
166
  }
@@ -29,7 +29,7 @@ function stripSection(text) {
29
29
  */
30
30
  function stripSignatures(text) {
31
31
  let t = text
32
- for (let i = 0; i < 2; i++) t = t.replace(/([`\w$.]+)\([^()]*\)/g, '$1')
32
+ for (let i = 0; i < 2; i++) t = t.replaceAll(/([`\w$.]+)\([^()]*\)/g, '$1')
33
33
  return t
34
34
  }
35
35
 
@@ -40,7 +40,7 @@ function parseSections(md) {
40
40
  for (const line of md.split('\n')) {
41
41
  const m = line.match(/^##\s+(.+)/)
42
42
  if (m) {
43
- cur = m[1].toLowerCase().replace(/[^а-яіїєґa-z0-9]/gi, '')
43
+ cur = m[1].toLowerCase().replaceAll(/[^а-яіїєґa-z0-9]/gi, '')
44
44
  result[cur] = ''
45
45
  } else if (cur) result[cur] += line + '\n'
46
46
  }
@@ -185,12 +185,12 @@ export async function generateDoc(
185
185
  r = facts.unsupported
186
186
  ? piOneShot(facts, src, model, LOCAL_TIMEOUT_MS)
187
187
  : piOrchestrated(facts, src, model, LOCAL_TIMEOUT_MS)
188
- } catch (e) {
188
+ } catch (error) {
189
189
  if (cloudModel) {
190
190
  const r2 = piOneShot(facts, src, cloudModel)
191
- return { ...r2, ms: Date.now() - t0, score: null, issues: [`tier1-error: ${e.message}`], tier: 2, model: cloudModel }
191
+ return { ...r2, ms: Date.now() - t0, score: null, issues: [`tier1-error: ${error.message}`], tier: 2, model: cloudModel }
192
192
  }
193
- throw e
193
+ throw error
194
194
  }
195
195
 
196
196
  // Stage 2.5: детермінований скоринг (0 токенів) — gate перед Tier 2
@@ -211,9 +211,9 @@ if (isRunAsCli(import.meta.url)) {
211
211
  const file = args.find(a => !a.startsWith('--'))
212
212
  const tierOnly = args.includes('--tier-only')
213
213
  const mi = args.indexOf('--model')
214
- const model = mi >= 0 ? args[mi + 1] : DEFAULT_LOCAL_MODEL
214
+ const model = mi !== -1 ? args[mi + 1] : DEFAULT_LOCAL_MODEL
215
215
  const si = args.indexOf('--sym-threshold')
216
- const symThreshold = si >= 0 ? Number(args[si + 1]) : DEFAULT_SYM_THRESHOLD
216
+ const symThreshold = si !== -1 ? Number(args[si + 1]) : DEFAULT_SYM_THRESHOLD
217
217
  if (!file) {
218
218
  console.error('Usage: node docgen-gen.mjs <file> [--model <m>] [--sym-threshold N] [--tier-only]')
219
219
  process.exit(1)