@nitra/cursor 4.1.2 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/docs/n-cursor.md +1 -9
  3. package/bin/n-cursor.js +3 -25
  4. package/package.json +1 -1
  5. package/rules/docker/lib/docs/docker-mirror.md +1 -1
  6. package/rules/docker/lib/docs/docker-native-addon.md +1 -1
  7. package/rules/npm-module/npm-module.mdc +1 -1
  8. package/rules/npm-module/policy/npm_publish_yml/template/npm-publish.yml.snippet.yml +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/docs/flow.MD +0 -1364
  19. package/scripts/dispatcher/docs/graph.md +0 -346
  20. package/scripts/dispatcher/docs/index.md +0 -236
  21. package/scripts/dispatcher/docs/trace.md +0 -296
  22. package/scripts/dispatcher/graph/lib/cmd-init.mjs +0 -112
  23. package/scripts/dispatcher/graph/lib/cmd-invalidate.mjs +0 -96
  24. package/scripts/dispatcher/graph/lib/cmd-kill.mjs +0 -141
  25. package/scripts/dispatcher/graph/lib/cmd-plan.mjs +0 -142
  26. package/scripts/dispatcher/graph/lib/cmd-run.mjs +0 -328
  27. package/scripts/dispatcher/graph/lib/cmd-scan.mjs +0 -115
  28. package/scripts/dispatcher/graph/lib/cmd-setup.mjs +0 -111
  29. package/scripts/dispatcher/graph/lib/cmd-signals.mjs +0 -328
  30. package/scripts/dispatcher/graph/lib/cmd-status.mjs +0 -131
  31. package/scripts/dispatcher/graph/lib/cmd-verify.mjs +0 -100
  32. package/scripts/dispatcher/graph/lib/cmd-watch.mjs +0 -128
  33. package/scripts/dispatcher/graph/lib/config.mjs +0 -103
  34. package/scripts/dispatcher/graph/lib/frontmatter.mjs +0 -224
  35. package/scripts/dispatcher/graph/lib/nnn.mjs +0 -127
  36. package/scripts/dispatcher/graph/lib/node-state.mjs +0 -157
  37. package/scripts/dispatcher/graph/lib/scanner.mjs +0 -235
  38. package/scripts/dispatcher/graph/lib/worktree-ops.mjs +0 -193
  39. package/scripts/dispatcher/graph-tasks.mjs +0 -92
  40. package/scripts/dispatcher/graph.mjs +0 -212
  41. package/scripts/dispatcher/index.mjs +0 -45
  42. package/scripts/dispatcher/lib/docs/active.md +0 -348
  43. package/scripts/dispatcher/lib/docs/artifact.md +0 -232
  44. package/scripts/dispatcher/lib/docs/budget.md +0 -167
  45. package/scripts/dispatcher/lib/docs/capability.md +0 -196
  46. package/scripts/dispatcher/lib/docs/commands.md +0 -210
  47. package/scripts/dispatcher/lib/docs/events.md +0 -183
  48. package/scripts/dispatcher/lib/docs/executor.md +0 -190
  49. package/scripts/dispatcher/lib/docs/gate.md +0 -231
  50. package/scripts/dispatcher/lib/docs/level.md +0 -335
  51. package/scripts/dispatcher/lib/docs/plan-panel.md +0 -181
  52. package/scripts/dispatcher/lib/docs/plan.md +0 -200
  53. package/scripts/dispatcher/lib/docs/planner.md +0 -269
  54. package/scripts/dispatcher/lib/docs/review.md +0 -255
  55. package/scripts/dispatcher/lib/docs/reviewer.md +0 -240
  56. package/scripts/dispatcher/lib/docs/snapshot.md +0 -247
  57. package/scripts/dispatcher/lib/docs/spec.md +0 -203
  58. package/scripts/dispatcher/lib/docs/state-store.md +0 -303
  59. package/scripts/dispatcher/lib/docs/subagent-runner.md +0 -173
  60. package/scripts/dispatcher/lib/events.mjs +0 -67
  61. package/scripts/dispatcher/lib/executor.mjs +0 -107
  62. package/scripts/dispatcher/lib/plan-panel.mjs +0 -76
  63. package/scripts/dispatcher/lib/state-store.mjs +0 -173
  64. package/scripts/dispatcher/lib/subagent-runner.mjs +0 -53
  65. package/scripts/graph/index.mjs +0 -115
  66. package/scripts/graph/lib/config.mjs +0 -62
  67. package/scripts/graph/lib/dag.mjs +0 -161
  68. package/scripts/graph/lib/frontmatter.mjs +0 -70
  69. package/scripts/graph/lib/nnn.mjs +0 -77
  70. package/scripts/graph/lib/state.mjs +0 -110
  71. package/scripts/graph/scan.mjs +0 -64
  72. package/scripts/graph/status.mjs +0 -86
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.0.1] - 2026-06-09
4
+
5
+ ### Fixed
6
+
7
+ - npm-module/npm_publish_yml: крок Release у канонічному сніпеті викликає бінарник `n-cursor release` з PATH замість `node npm/bin/n-cursor.js release` — шлях npm/bin/n-cursor.js не існує у downstream-споживачів і ламав їхній npm-publish (`Cannot find module .../npm/bin/n-cursor.js`, напр. @7n/n)
8
+ - ci/setup-bun-deps: синхронізовано `bun.lock` з `package.json` — застарілий lockfile валив `bun install --frozen-lockfile` у workflow `npm-publish` ще до кроку release, блокуючи публікацію
9
+ - npm-module/npm_publish_yml: крок Release викликає `bunx n-cursor release` замість `n-cursor release` — голий workflow-`run:` не має `node_modules/.bin` у PATH (на відміну від npm/bun-скриптів), тож `n-cursor` падав з `command not found` (exit 127); `bunx` резолвить бінарник із node_modules і працює і в @nitra/cursor, і в downstream-споживачів
10
+
11
+ ## [5.0.0] - 2026-06-08
12
+
13
+ ### Changed
14
+
15
+ - docs: додати план extraction Meta-task
16
+
17
+ ### Fixed
18
+
19
+ - coverage `--changed` більше не залежить від runtime-стану `.flow.json`; scope визначається через git merge-base
20
+
21
+ ### Removed
22
+
23
+ - видалено вбудовані flow і graph; Meta-task винесено в окремий пакет @7n/mt
24
+
3
25
  ## [4.1.2] - 2026-06-08
4
26
 
5
27
  ### Added
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "4.1.2",
3
+ "version": "5.0.1",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -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
 
@@ -65,7 +65,7 @@ bunx -p typescript tsc src/**/*.js --declaration --allowJs --emitDeclarationOnly
65
65
 
66
66
  **`npm-publish.yml`:** push у **`main`**, **`on.push.paths`** з **`npm/**`**, **`JS-DevTools/npm-publish@v4.1.5`**, **`with.package: npm/package.json`**, **`permissions.id-token: write`** (OIDC на npm).
67
67
 
68
- Workflow робить **release + publish** одним job (`release-publish`): крок **`Release (bump + CHANGELOG + tag)`** (`node npm/bin/n-cursor.js release` — агрегує change-файли, bump `version`, генерує секцію `CHANGELOG.md`, ставить git-тег) виконується **перед** публікацією. Тому потрібні **`permissions.contents: write`** і **`persist-credentials: true`** з **`fetch-depth: 0`** на `checkout` (release пушить commit-back версії та тег), а також локальний composite **`./.github/actions/setup-bun-deps`** і крок `Configure git identity`. Це узгоджено з **`n-changelog`**: `version`/`CHANGELOG.md` змінює лише `n-cursor release` у CI на `main`. Програмна перевірка (`npm_module.npm_publish_yml`) звіряє **весь канонічний сніпет** напряму (`target.json:"check":"template"`, generic deep-subset): усі поля й кроки сніпета (`on.push.paths`/`branches`, `concurrency`, `permissions.contents/id-token`, `checkout` з `persist-credentials/fetch-depth`, `setup-bun-deps`, `Configure git identity`, `Release`, publish-крок) **обовʼязкові**; зайві кроки/поля дозволені (subset-of), масиви матчаться за наявністю (порядок кроків не важить). Сніпет — єдине джерело істини: його редагування одразу змінює enforce, без правок rego й без міграторів.
68
+ Workflow робить **release + publish** одним job (`release-publish`): крок **`Release (bump + CHANGELOG + tag)`** (`bunx n-cursor release` — агрегує change-файли, bump `version`, генерує секцію `CHANGELOG.md`, ставить git-тег) виконується **перед** публікацією. Тому потрібні **`permissions.contents: write`** і **`persist-credentials: true`** з **`fetch-depth: 0`** на `checkout` (release пушить commit-back версії та тег), а також локальний composite **`./.github/actions/setup-bun-deps`** і крок `Configure git identity`. Це узгоджено з **`n-changelog`**: `version`/`CHANGELOG.md` змінює лише `n-cursor release` у CI на `main`. Програмна перевірка (`npm_module.npm_publish_yml`) звіряє **весь канонічний сніпет** напряму (`target.json:"check":"template"`, generic deep-subset): усі поля й кроки сніпета (`on.push.paths`/`branches`, `concurrency`, `permissions.contents/id-token`, `checkout` з `persist-credentials/fetch-depth`, `setup-bun-deps`, `Configure git identity`, `Release`, publish-крок) **обовʼязкові**; зайві кроки/поля дозволені (subset-of), масиви матчаться за наявністю (порядок кроків не важить). Сніпет — єдине джерело істини: його редагування одразу змінює enforce, без правок rego й без міграторів.
69
69
 
70
70
  - Канон: [npm-publish.yml.snippet.yml](./policy/npm_publish_yml/template/npm-publish.yml.snippet.yml)
71
71
 
@@ -37,7 +37,7 @@ jobs:
37
37
  git config user.email "github-actions[bot]@users.noreply.github.com"
38
38
 
39
39
  - name: Release (bump + CHANGELOG + tag)
40
- run: node npm/bin/n-cursor.js release
40
+ run: bunx n-cursor release
41
41
 
42
42
  - name: Publish package
43
43
  uses: JS-DevTools/npm-publish@v4.1.5
@@ -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)