@nitra/cursor 4.1.0 → 4.1.2

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 (135) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/n-cursor.js +25 -13
  3. package/docs/flow.MD +1364 -0
  4. package/docs/stryker.config.md +37 -0
  5. package/docs/vitest.config.md +23 -0
  6. package/lib/models.mjs +1 -2
  7. package/package.json +2 -1
  8. package/rules/abie/fix.mjs +1 -1
  9. package/rules/bun/docs/fix.md +3 -0
  10. package/rules/bun/fix.mjs +1 -1
  11. package/rules/capacitor/fix.mjs +1 -1
  12. package/rules/changelog/docs/fix.md +3 -0
  13. package/rules/changelog/fix.mjs +1 -1
  14. package/rules/ci4/fix.mjs +1 -1
  15. package/rules/ci4/js/docs/marksman_config.md +1 -0
  16. package/rules/docker/docs/fix.md +1 -1
  17. package/rules/docker/fix.mjs +1 -1
  18. package/rules/docker/lint/docs/lint.md +1 -0
  19. package/rules/efes/docs/fix.md +2 -1
  20. package/rules/efes/fix.mjs +1 -1
  21. package/rules/feedback/fix.mjs +1 -1
  22. package/rules/ga/fix.mjs +1 -1
  23. package/rules/ga/js/lint.mjs +1 -1
  24. package/rules/graphql/docs/fix.md +4 -1
  25. package/rules/graphql/fix.mjs +1 -1
  26. package/rules/graphql/lib/docs/graphql-gql-scan.md +3 -0
  27. package/rules/hasura/fix.mjs +1 -1
  28. package/rules/image-avif/docs/fix.md +4 -1
  29. package/rules/image-avif/fix.mjs +1 -1
  30. package/rules/image-avif/js/docs/avif_generation.md +1 -0
  31. package/rules/image-compress/fix.mjs +1 -1
  32. package/rules/js-bun-db/fix.mjs +1 -1
  33. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +6 -0
  34. package/rules/js-bun-redis/fix.mjs +1 -1
  35. package/rules/js-lint/fix.mjs +1 -1
  36. package/rules/js-lint/js/docs/utils_imports.md +1 -0
  37. package/rules/js-lint-ci/docs/fix.md +4 -1
  38. package/rules/js-lint-ci/fix.mjs +1 -1
  39. package/rules/js-mssql/docs/fix.md +3 -0
  40. package/rules/js-mssql/fix.mjs +1 -1
  41. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +9 -0
  42. package/rules/js-run/docs/fix.md +3 -0
  43. package/rules/js-run/fix.mjs +1 -1
  44. package/rules/js-run/lib/docs/check-env-scan.md +2 -1
  45. package/rules/js-run/lib/docs/promise-settimeout-scan.md +4 -0
  46. package/rules/k8s/docs/fix.md +3 -0
  47. package/rules/k8s/fix.mjs +1 -1
  48. package/rules/nginx-default-tpl/docs/fix.md +3 -0
  49. package/rules/nginx-default-tpl/fix.mjs +1 -1
  50. package/rules/npm-module/fix.mjs +1 -1
  51. package/rules/npm-module/js/header_doc_pointer.mjs +14 -3
  52. package/rules/php/docs/fix.md +2 -1
  53. package/rules/php/fix.mjs +1 -1
  54. package/rules/python/docs/fix.md +4 -1
  55. package/rules/python/fix.mjs +1 -1
  56. package/rules/rego/fix.mjs +1 -1
  57. package/rules/rego/js/lint.mjs +1 -1
  58. package/rules/release/docs/fix.md +4 -1
  59. package/rules/release/fix.mjs +1 -1
  60. package/rules/rust/fix.mjs +1 -1
  61. package/rules/security/docs/fix.md +1 -1
  62. package/rules/security/fix.mjs +1 -1
  63. package/rules/style-lint/docs/fix.md +3 -0
  64. package/rules/style-lint/fix.mjs +1 -1
  65. package/rules/tauri/docs/fix.md +4 -1
  66. package/rules/tauri/fix.mjs +1 -1
  67. package/rules/test/docs/fix.md +3 -0
  68. package/rules/test/fix.mjs +1 -1
  69. package/rules/test/js/no-relative-fs-path.mjs +2 -1
  70. package/rules/text/docs/fix.md +3 -0
  71. package/rules/text/fix.mjs +1 -1
  72. package/rules/text/js/lint.mjs +1 -1
  73. package/rules/vue/fix.mjs +1 -1
  74. package/rules/worktree/fix.mjs +1 -1
  75. package/scripts/auto-rules.mjs +1 -1
  76. package/scripts/coverage-classify/index.mjs +10 -10
  77. package/scripts/coverage-fix.mjs +2 -2
  78. package/scripts/dispatcher/graph/lib/cmd-init.mjs +112 -0
  79. package/scripts/dispatcher/graph/lib/cmd-invalidate.mjs +96 -0
  80. package/scripts/dispatcher/graph/lib/cmd-kill.mjs +141 -0
  81. package/scripts/dispatcher/graph/lib/cmd-plan.mjs +142 -0
  82. package/scripts/dispatcher/graph/lib/cmd-run.mjs +328 -0
  83. package/scripts/dispatcher/graph/lib/cmd-scan.mjs +115 -0
  84. package/scripts/dispatcher/graph/lib/cmd-setup.mjs +111 -0
  85. package/scripts/dispatcher/graph/lib/cmd-signals.mjs +328 -0
  86. package/scripts/dispatcher/graph/lib/cmd-status.mjs +131 -0
  87. package/scripts/dispatcher/graph/lib/cmd-verify.mjs +100 -0
  88. package/scripts/dispatcher/graph/lib/cmd-watch.mjs +128 -0
  89. package/scripts/dispatcher/graph/lib/config.mjs +103 -0
  90. package/scripts/dispatcher/graph/lib/frontmatter.mjs +224 -0
  91. package/scripts/dispatcher/graph/lib/nnn.mjs +127 -0
  92. package/scripts/dispatcher/graph/lib/node-state.mjs +157 -0
  93. package/scripts/dispatcher/graph/lib/scanner.mjs +235 -0
  94. package/scripts/dispatcher/graph/lib/worktree-ops.mjs +193 -0
  95. package/scripts/dispatcher/graph-tasks.mjs +92 -0
  96. package/scripts/dispatcher/index.mjs +3 -3
  97. package/scripts/dispatcher/lib/docs/events.md +1 -0
  98. package/scripts/dispatcher/lib/executor.mjs +1 -1
  99. package/scripts/dispatcher/lib/subagent-runner.mjs +9 -9
  100. package/scripts/dispatcher/trace.mjs +6 -2
  101. package/scripts/docs/build-agents-commands.md +1 -0
  102. package/scripts/docs/cli-entry.md +6 -0
  103. package/scripts/graph/index.mjs +115 -0
  104. package/scripts/graph/lib/config.mjs +62 -0
  105. package/scripts/graph/lib/dag.mjs +161 -0
  106. package/scripts/graph/lib/frontmatter.mjs +70 -0
  107. package/scripts/graph/lib/nnn.mjs +77 -0
  108. package/scripts/graph/lib/state.mjs +110 -0
  109. package/scripts/graph/scan.mjs +64 -0
  110. package/scripts/graph/status.mjs +86 -0
  111. package/scripts/lib/docs/load-cursor-config.md +3 -0
  112. package/scripts/lib/root-notice.mjs +4 -2
  113. package/scripts/lib/rule-predicates.mjs +1 -1
  114. package/scripts/lib/worktree-notice.mjs +14 -7
  115. package/scripts/lib/worktree.mjs +3 -2
  116. package/scripts/utils/resolve-js-root.mjs +2 -1
  117. package/scripts/utils/with-lock.mjs +1 -1
  118. package/skills/docgen/js/docgen-batch.mjs +7 -7
  119. package/skills/docgen/js/docgen-extract.mjs +80 -37
  120. package/skills/docgen/js/docgen-ignore.mjs +1 -1
  121. package/skills/docgen/js/docgen-prompts.mjs +21 -5
  122. package/skills/fix/js/llm-worker.mjs +19 -22
  123. package/skills/fix/js/orchestrator.mjs +6 -7
  124. package/skills/fix/js/t0.mjs +14 -13
  125. package/types/bin/n-cursor.d.ts +1 -1
  126. package/rules/flow/docs/fix.md +0 -152
  127. package/rules/flow/fix.mjs +0 -18
  128. package/rules/flow/flow.mdc +0 -127
  129. package/rules/flow/meta.json +0 -1
  130. package/scripts/dispatcher/lib/docs/flow-lock.md +0 -161
  131. package/scripts/dispatcher/lib/docs/flow-resolve.md +0 -267
  132. package/scripts/dispatcher/lib/flow-plan.mjs +0 -153
  133. package/scripts/dispatcher/lib/flow-resolve.mjs +0 -156
  134. package/scripts/dispatcher/lib/flow-signals.mjs +0 -235
  135. package/scripts/dispatcher/lib/flow-verify.mjs +0 -127
@@ -6,11 +6,15 @@ export const STYLE = [
6
6
  'Заборонено: сигнатури, типи, параметри функцій; перелік stdlib-модулів; опис regex чи внутрішніх приватних імен.'
7
7
  ].join(' ')
8
8
 
9
- /** Короткий людиночитний витяг фактів (без коду). */
9
+ /**
10
+ * Короткий людиночитний витяг фактів (без коду).
11
+ * @param {object} facts факт-лист про файл
12
+ * @returns {string} текстовий блок «factsTxt» для system-prompt
13
+ */
10
14
  function factsSummary(facts) {
11
15
  const m = facts.markers || {}
12
16
  const lines = []
13
- if (facts.header) lines.push(`Намір файлу: ${facts.header.replace(/\n/g, ' ')}`)
17
+ if (facts.header) lines.push(`Намір файлу: ${facts.header.replaceAll('\n', ' ')}`)
14
18
  if (facts.exports?.length) lines.push(`Публічні функції: ${facts.exports.map(e => e.name).join(', ')}`)
15
19
  if (m.skips?.length) lines.push(`Свідомо пропускає шляхи: ${m.skips.join(', ')}`)
16
20
  lines.push(`Read-only: ${m.readOnly ? 'так' : 'ні'}`)
@@ -30,7 +34,9 @@ const msgs = (system, user) => [
30
34
  /**
31
35
  * Секційні набори messages з МІНІМАЛЬНИМ контекстом під кожну секцію.
32
36
  * Код потрапляє лише в `behavior`; решта секцій — на факт-листі.
33
- * @returns {Array<{key:string, messages:object[], numPredict:number}>}
37
+ * @param {object} facts факт-лист про файл
38
+ * @param {string} src вміст файлу
39
+ * @returns {Array<{key:string, messages:object[], numPredict:number}>} набір секційних промптів
34
40
  */
35
41
  export function sectionMessages(facts, src) {
36
42
  const factsTxt = factsSummary(facts)
@@ -83,7 +89,12 @@ export function sectionMessages(facts, src) {
83
89
  return out
84
90
  }
85
91
 
86
- /** One-shot messages (база для порівняння). */
92
+ /**
93
+ * One-shot messages (база для порівняння).
94
+ * @param {object} facts факт-лист про файл
95
+ * @param {string} src вміст файлу
96
+ * @returns {Array<object>} messages-масив для LLM API
97
+ */
87
98
  export function oneShotMessages(facts, src) {
88
99
  const multi = (facts.exports?.length || 0) > 1
89
100
  return msgs(
@@ -92,7 +103,12 @@ export function oneShotMessages(facts, src) {
92
103
  )
93
104
  }
94
105
 
95
- /** Лише текст user-промпту для one-shot (для хмарного fallback через Anthropic SDK). */
106
+ /**
107
+ * Лише текст user-промпту для one-shot (для хмарного fallback через Anthropic SDK).
108
+ * @param {object} facts факт-лист про файл
109
+ * @param {string} src вміст файлу
110
+ * @returns {string} plain-text user-prompt
111
+ */
96
112
  export function oneShotPromptText(facts, src) {
97
113
  const multi = (facts.exports?.length || 0) > 1
98
114
  return `Напиши документацію для файлу. Секції: ## Огляд (1-3 речення), ## Поведінка (нумерований/маркований алгоритм), ${multi ? '## Публічний API (назва + що робить), ' : ''}## Гарантії поведінки.\n\nФАЙЛ ${facts.relPath}:\n\`\`\`\n${src}\n\`\`\``
@@ -11,10 +11,11 @@ import { resolveModel } from '../../../lib/models.mjs'
11
11
  export const MODEL = env.N_CURSOR_FIX_MODEL ?? resolveModel('min')
12
12
  export const MODEL_HEAVY = env.N_CURSOR_FIX_MODEL_HEAVY ?? resolveModel('avg')
13
13
 
14
+ const JSON_CODE_BLOCK_RE = /```(?:json)?\s*([\s\S]*?)```/
15
+
14
16
  /**
15
17
  * Витягує відносні шляхи файлів із violation output.
16
18
  * Розуміє workspace-prefix: `[npm] skills/foo.mjs` → `npm/skills/foo.mjs`.
17
- *
18
19
  * @param {string} output violation output з fix check
19
20
  * @returns {string[]} унікальні відносні шляхи (від кореня проєкту)
20
21
  */
@@ -23,7 +24,7 @@ function extractFilePaths(output) {
23
24
  const results = []
24
25
 
25
26
  // Патерн з workspace: [npm] skills/foo.mjs або [demo] src/bar.ts
26
- const wsRe = /\[([\w-]+)\]\s+([\w./][\w./\-]*\.(?:json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py))(?::\d+)?/gm
27
+ const wsRe = /\[([\w-]+)\]\s+([\w./][\w./-]*\.(?:json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py))(?::\d+)?/gm
27
28
  for (const m of output.matchAll(wsRe)) {
28
29
  const p = `${m[1]}/${m[2]}`
29
30
  if (!seen.has(p)) {
@@ -33,7 +34,7 @@ function extractFilePaths(output) {
33
34
  }
34
35
 
35
36
  // Патерн без workspace: просто path/to/file.ext або ./file.ext
36
- const re = /(?:^|\s)(\.?[\w][\w./\-]*\.(?:json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py))(?::\d+)?/gm
37
+ const re = /(?:^|\s)(\.?[\w][\w./-]*\.(?:json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py))(?::\d+)?/gm
37
38
  for (const m of output.matchAll(re)) {
38
39
  const p = m[1]
39
40
  if (!seen.has(p)) {
@@ -47,12 +48,11 @@ function extractFilePaths(output) {
47
48
 
48
49
  /**
49
50
  * Будує prompt для pi: правило + порушення + поточний вміст файлів.
50
- *
51
- * @param {string} ruleId
51
+ * @param {string} ruleId ID правила
52
52
  * @param {string} ruleMdc вміст .mdc-файлу правила
53
53
  * @param {string} output violation output
54
- * @param {Array<{path:string, content:string}>} files
55
- * @returns {string}
54
+ * @param {Array<{path:string, content:string}>} files прочитані файли (path + content)
55
+ * @returns {string} текст промпта для pi
56
56
  */
57
57
  function buildPrompt(ruleId, ruleMdc, output, files) {
58
58
  const filesBlock =
@@ -87,10 +87,9 @@ function buildPrompt(ruleId, ruleMdc, output, files) {
87
87
 
88
88
  /**
89
89
  * Запускає pi і повертає stdout як рядок.
90
- *
91
- * @param {string} prompt
92
- * @param {string} model
93
- * @returns {{ text: string, error?: string }}
90
+ * @param {string} prompt текст промпта
91
+ * @param {string} model назва моделі (provider/id)
92
+ * @returns {{ text: string, error?: string }} stdout pi або повідомлення про помилку
94
93
  */
95
94
  function callPi(prompt, model) {
96
95
  const modelArgs = model ? ['--model', model] : []
@@ -120,9 +119,8 @@ function callPi(prompt, model) {
120
119
  /**
121
120
  * Парсить JSON-відповідь від pi.
122
121
  * pi може обгорнути JSON у ```json ... ```, тому пробуємо витягти.
123
- *
124
- * @param {string} text
125
- * @returns {{ changes: Array<{path:string,content:string}>, error?: string } | null}
122
+ * @param {string} text сирий stdout pi
123
+ * @returns {{ changes: Array<{path:string,content:string}>, error?: string } | null} розпарсений патч або null
126
124
  */
127
125
  function parseResponse(text) {
128
126
  // Спроба 1: прямий JSON
@@ -133,7 +131,7 @@ function parseResponse(text) {
133
131
  }
134
132
 
135
133
  // Спроба 2: витягти з ```json ... ```
136
- const m = text.match(/```(?:json)?\s*([\s\S]*?)```/)
134
+ const m = text.match(JSON_CODE_BLOCK_RE)
137
135
  if (m) {
138
136
  try {
139
137
  return JSON.parse(m[1].trim())
@@ -158,14 +156,13 @@ function parseResponse(text) {
158
156
 
159
157
  /**
160
158
  * LLM-worker: виправляє одне rule-порушення через pi (C1 pattern).
161
- *
162
- * @param {string} ruleId
159
+ * @param {string} ruleId ID правила
163
160
  * @param {string} violationOutput output з fix check для цього rule
164
161
  * @param {string} projectRoot абсолютний шлях до кореня проєкту
165
- * @param {{ model?: string }} opts
166
- * @returns {Promise<{ ok: boolean, error?: string }>}
162
+ * @param {{ model?: string }} opts опції (model — перевизначення моделі)
163
+ * @returns {Promise<{ ok: boolean, error?: string }>} статус виправлення і можлива помилка
167
164
  */
168
- export async function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
165
+ export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
169
166
  const model = opts.model ?? MODEL
170
167
 
171
168
  // 1. Читаємо rule .mdc
@@ -207,8 +204,8 @@ export async function runLlmWorker(ruleId, violationOutput, projectRoot, opts =
207
204
  const abs = join(projectRoot, change.path)
208
205
  try {
209
206
  writeFileSync(abs, change.content, 'utf8')
210
- } catch (e) {
211
- return { ok: false, error: `write ${change.path}: ${e.message}` }
207
+ } catch (error) {
208
+ return { ok: false, error: `write ${change.path}: ${error.message}` }
212
209
  }
213
210
  }
214
211
 
@@ -20,8 +20,8 @@ export async function runOrchestratorCli(args, cwd) {
20
20
 
21
21
  const maxIterIdx = args.indexOf('--max-iter')
22
22
  const maxIter =
23
- maxIterIdx !== -1 ? Number(args[maxIterIdx + 1] ?? DEFAULT_MAX_ITER) || DEFAULT_MAX_ITER : DEFAULT_MAX_ITER
24
- const skipIdxs = new Set(maxIterIdx !== -1 ? [maxIterIdx, maxIterIdx + 1] : [])
23
+ maxIterIdx === -1 ? DEFAULT_MAX_ITER : (Number(args[maxIterIdx + 1] ?? DEFAULT_MAX_ITER) || DEFAULT_MAX_ITER)
24
+ const skipIdxs = new Set(maxIterIdx === -1 ? [] : [maxIterIdx, maxIterIdx + 1])
25
25
  const ruleFilter = args.filter((a, i) => !a.startsWith('-') && !skipIdxs.has(i))
26
26
 
27
27
  /** @type {Map<string, number>} ruleId → кількість LLM-провалів підряд */
@@ -53,7 +53,7 @@ export async function runOrchestratorCli(args, cwd) {
53
53
 
54
54
  const afterT0 = runFixCheck(cwd, ruleFilter)
55
55
  const failedAfterT0 = afterT0?.rules.filter(r => !r.ok) ?? failed
56
- const t0Fixed = failed.filter(r => !failedAfterT0.find(f => f.ruleId === r.ruleId))
56
+ const t0Fixed = failed.filter(r => !failedAfterT0.some(f => f.ruleId === r.ruleId))
57
57
 
58
58
  if (t0Fixed.length > 0) {
59
59
  console.log(` ⚙️ T0-auto: ${t0Fixed.map(r => r.ruleId).join(', ')}`)
@@ -98,10 +98,9 @@ export async function runOrchestratorCli(args, cwd) {
98
98
  /**
99
99
  * Внутрішня check-gate: запускає fix-перевірки і повертає структурований результат.
100
100
  * Не є публічним CLI — викликається лише оркестратором.
101
- *
102
- * @param {string} cwd
103
- * @param {string[]} ruleFilter
104
- * @returns {{ total: number, failed: number, rules: Array<{ ruleId: string, ok: boolean, output: string }> } | null}
101
+ * @param {string} cwd корінь проєкту
102
+ * @param {string[]} ruleFilter список ID правил (порожній — усі)
103
+ * @returns {{ total: number, failed: number, rules: Array<{ ruleId: string, ok: boolean, output: string }> } | null} JSON-результат або null якщо stdout порожній/невалідний
105
104
  */
106
105
  function runFixCheck(cwd, ruleFilter = []) {
107
106
  const r = spawnSync('bun', [N_CURSOR_BIN, '_fix-check', ...ruleFilter], {
@@ -1,10 +1,14 @@
1
1
  /** @see ./docs/t0.md */
2
2
  import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
3
- import { join } from 'node:path'
3
+ import { dirname, join } from 'node:path'
4
4
  import { spawnSync } from 'node:child_process'
5
- import { dirname } from 'node:path'
6
5
  import { fileURLToPath } from 'node:url'
7
6
 
7
+ const REC_REQUIRE_RE = /recommendations має містити "[^"]+"/
8
+ const REC_MATCH_ALL_RE = /recommendations має містити "([^"]+)"/g
9
+ const FORBIDDEN_FILE_RE = /Знайдено заборонений файл: \S+/
10
+ const FORBIDDEN_FILE_MATCH_ALL_RE = /Знайдено заборонений файл: (\S+)/g
11
+
8
12
  /**
9
13
  * Патерни T0-auto.
10
14
  * Кожен паттерн: {
@@ -19,9 +23,9 @@ const PATTERNS = [
19
23
  // Fix: додати рядок у .vscode/extensions.json#recommendations
20
24
  {
21
25
  id: 'vscode-ext-add',
22
- test: out => /recommendations має містити "[^"]+"/.test(out),
26
+ test: out => REC_REQUIRE_RE.test(out),
23
27
  apply: (out, cwd) => {
24
- const matches = [...out.matchAll(/recommendations має містити "([^"]+)"/g)]
28
+ const matches = [...out.matchAll(REC_MATCH_ALL_RE)]
25
29
  if (matches.length === 0) return { ok: false, action: 'no match' }
26
30
 
27
31
  const extPath = join(cwd, '.vscode/extensions.json')
@@ -51,9 +55,9 @@ const PATTERNS = [
51
55
  // Fix: видалити файл
52
56
  {
53
57
  id: 'rm-forbidden-file',
54
- test: out => /Знайдено заборонений файл: \S+/.test(out),
58
+ test: out => FORBIDDEN_FILE_RE.test(out),
55
59
  apply: (out, cwd) => {
56
- const matches = [...out.matchAll(/Знайдено заборонений файл: (\S+)/g)]
60
+ const matches = [...out.matchAll(FORBIDDEN_FILE_MATCH_ALL_RE)]
57
61
  if (matches.length === 0) return { ok: false, action: 'no match' }
58
62
 
59
63
  const removed = []
@@ -72,11 +76,10 @@ const PATTERNS = [
72
76
 
73
77
  /**
74
78
  * Застосовує всі T0-auto паттерни до одного violation-output.
75
- *
76
79
  * @param {string} ruleId id правила (для логу)
77
80
  * @param {string} violationOutput рядок з поля `output` у `fix --json`
78
81
  * @param {string} cwd корінь проєкту
79
- * @returns {{ applied: boolean, actions: string[] }}
82
+ * @returns {{ applied: boolean, actions: string[] }} результат: чи щось застосовано і список дій
80
83
  */
81
84
  export function applyT0Auto(ruleId, violationOutput, cwd) {
82
85
  const actions = []
@@ -97,9 +100,8 @@ export function applyT0Auto(ruleId, violationOutput, cwd) {
97
100
  /**
98
101
  * Повертає список id правил, для яких є хоча б один T0-auto паттерн
99
102
  * (визначається по violation-output із `fix --json`).
100
- *
101
- * @param {{ ruleId: string, output: string }[]} failedRules
102
- * @returns {string[]}
103
+ * @param {{ ruleId: string, output: string }[]} failedRules повний список правил із output
104
+ * @returns {string[]} ID правил із наявним T0-auto патерном
103
105
  */
104
106
  export function filterT0AutoRules(failedRules) {
105
107
  return failedRules.filter(r => PATTERNS.some(p => p.test(r.output))).map(r => r.ruleId)
@@ -115,12 +117,11 @@ const N_CURSOR_BIN = join(HERE, '../../../bin/n-cursor.js')
115
117
  * CLI підкоманда `n-cursor fix-t0 [rule...]`.
116
118
  * Запускає `fix --json`, застосовує T0-auto для кожного violation,
117
119
  * повторно перевіряє check-gate, виводить підсумок.
118
- *
119
120
  * @param {string[]} args аргументи підкоманди (опційний список rule-ids)
120
121
  * @param {string} cwd корінь проєкту
121
122
  * @returns {Promise<number>} 0 — T0-auto закрив всі або немає порушень; 1 — лишились
122
123
  */
123
- export async function runT0AutoCli(args, cwd) {
124
+ export function runT0AutoCli(args, cwd) {
124
125
  const ruleFilter = args.filter(a => !a.startsWith('--'))
125
126
  const verbose = args.includes('--verbose') || args.includes('-v')
126
127
 
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {}
2
+ export {};
@@ -1,152 +0,0 @@
1
- # `npm/rules/flow/fix.mjs`
2
-
3
- ## Огляд
4
-
5
- Файл є точкою входу (entry point) правила `flow` у каталозі правил пакета `@nitra/cursor`. Правило `flow` належить до категорії **pure-doc contract-правил**: воно не має програмних concern-ів і фактично валідує лише супровідний документ `.mdc` (Markdown-Contract) у тій самій теці.
6
-
7
- Модуль виконує дві основні задачі:
8
-
9
- 1. Експортує функцію `run(ctx)`, яка делегує всю роботу стандартному пайплайну виконання правил (`runStandardRule`). Цей експорт використовується диспетчером CLI `@nitra/cursor` (наприклад, командою `npx @nitra/cursor fix flow`) та внутрішніми оркестраторами правил.
10
- 2. Підтримує **standalone-режим** — якщо файл запущено напряму як CLI-скрипт (через `bun rules/flow/fix.mjs`), він викликає універсальний CLI-раннер `runRuleCli` і завершує процес коректним exit-кодом, придатним для CI/IDE-інтеграцій.
11
-
12
- Стандартний пайплайн правила, який запускає `runStandardRule`, виглядає так (порядок фіксований):
13
- `applies` → `JS-concerns` → `policy` → `mdc-refs`. Оскільки в цьому правилі програмних concern-ів немає, реальна перевірка зводиться до етапів `applies`, `policy` та валідації посилань у `.mdc`-файлі.
14
-
15
- ## Експорти / API
16
-
17
- Модуль є **ES-модулем** (`.mjs`), має такі експорти:
18
-
19
- | Експорт | Тип | Призначення |
20
- | ------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
21
- | `run` | `function(ctx?: RuleContext): Promise<number>` | Іменований експорт; основна точка входу для виконання правила. Викликається диспетчером правил `@nitra/cursor`. |
22
-
23
- Side-effect верхнього рівня (не експорт, але частина публічної поведінки модуля):
24
-
25
- - Якщо `import.meta.url` відповідає стартовому файлу процесу (`isRunAsCli`), модуль запускає `runRuleCli(import.meta.dirname)` та викликає `process.exit(...)` із кодом, який повернув раннер.
26
-
27
- Default-експорту **немає**.
28
-
29
- ## Функції
30
-
31
- ### `run(ctx)`
32
-
33
- ```js
34
- export function run(ctx)
35
- ```
36
-
37
- - **Призначення.** Запустити стандартизований пайплайн правила `flow` для поточної теки (`import.meta.dirname` — каталог, де лежить `fix.mjs`, тобто `npm/rules/flow/`).
38
- - **Параметри:**
39
- - `ctx` _(необов'язковий, `RuleContext`)_ — контекст прогону правил. Тип `RuleContext` імпортується з `../../scripts/lib/run-standard-rule.mjs`. У контексті, зокрема, передаються спільні структури між правилами — наприклад, `walkCache` (кеш обходу файлової системи), щоб не повторювати дорогі сканування при послідовному запуску багатьох правил.
40
- - **Повертає:** `Promise<number>`.
41
- - `0` — правило виконано успішно, порушень не виявлено.
42
- - `1` — знайдено порушення (наприклад, проблеми у відповідному `.mdc`-файлі).
43
- - **Side effects:** жодних безпосередньо у функції; усі побічні ефекти (читання FS, виведення повідомлень, агрегація знахідок) інкапсульовані всередині `runStandardRule`.
44
- - **Контракт пайплайну, який вмикає `runStandardRule`:**
45
- - `applies` — визначає, чи правило застосовне до поточного проєкту/контексту.
46
- - `JS-concerns` — програмні перевірки JS/MJS-коду (у цьому правилі — фактично відсутні, бо правило pure-doc).
47
- - `policy` — політики/обмеження, спільні для всіх правил.
48
- - `mdc-refs` — валідація посилань і структури супровідного `.mdc`-файлу.
49
-
50
- ### IIFE-блок CLI-режиму (top-level, не функція)
51
-
52
- ```js
53
- if (isRunAsCli(import.meta.url)) {
54
- process.exit(await runRuleCli(import.meta.dirname))
55
- }
56
- ```
57
-
58
- - **Призначення.** Дозволити запуск файлу як самостійного скрипта (`bun rules/flow/fix.mjs`), повністю еквівалентний `npx @nitra/cursor fix flow`.
59
- - **Семантика умови.** `isRunAsCli(import.meta.url)` повертає `true`, якщо цей файл є стартовим модулем процесу (а не імпортованим іншим модулем). Це стандартний у пакеті паттерн dual-mode (бібліотека + CLI).
60
- - **Параметри `runRuleCli`:** передається `import.meta.dirname` — абсолютний шлях до каталогу `npm/rules/flow/`. Раннер сам читає `meta.json`/`fix.mjs`/`.mdc` із цього каталогу.
61
- - **Завершення.** `process.exit(...)` із цілочисельним exit-кодом, який повернув `runRuleCli`. Це важливо для CI/IDE: ненульовий код → провалена перевірка.
62
- - **ESLint-винятки в коді:** рядок із `process.exit` навмисно вимикає правила `n/no-process-exit` та `unicorn/no-process-exit` через коментар `// eslint-disable-next-line ... -- standalone entry-point має повертати exit-code для CI/IDE`. Це задокументоване відхилення: для standalone CLI вихід кодом потрібен.
63
-
64
- ## Залежності
65
-
66
- Усі залежності — **внутрішні**, з підкаталогу `scripts/lib/` пакета:
67
-
68
- | Імпорт | Шлях | Що дає |
69
- | ----------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
70
- | `isRunAsCli` | `../../scripts/lib/run-rule-cli.mjs` | Предикат: чи `import.meta.url` модуля збігається зі стартовим файлом процесу (визначає standalone-запуск). |
71
- | `runRuleCli` | `../../scripts/lib/run-rule-cli.mjs` | Універсальний CLI-раннер для одного правила: парсить аргументи, готує контекст, викликає `run`, форматує вивід, повертає exit-код. |
72
- | `runStandardRule` | `../../scripts/lib/run-standard-rule.mjs` | Стандартний пайплайн правил `applies → JS-concerns → policy → mdc-refs`; читає сусідній `.mdc`, виконує перевірки, агрегує результат у код повернення. |
73
-
74
- Зовнішніх (npm-пакетних) залежностей файл не імпортує.
75
-
76
- Опосередковано (через `runStandardRule`) використовуються:
77
-
78
- - Сусідні файли у каталозі `npm/rules/flow/` — насамперед `.mdc` (Markdown-Contract правила) та `meta.json` (метадані правила), якщо такі присутні.
79
- - Спільна інфраструктура з `npm/scripts/lib/` (логування, форматування результатів, утиліти FS-обходу).
80
-
81
- ## Потік виконання / Використання
82
-
83
- ### Сценарій 1. Виклик з диспетчера правил (типовий)
84
-
85
- 1. CLI `@nitra/cursor` (або інший оркестратор) визначає, що потрібно запустити правило `flow` у режимі `fix`.
86
- 2. Диспетчер імпортує `fix.mjs` як модуль і викликає `run(ctx)`, передаючи спільний `ctx` (наприклад, із заповненим `walkCache`).
87
- 3. `run` делегує виконання `runStandardRule(import.meta.dirname, ctx)`.
88
- 4. `runStandardRule` послідовно виконує етапи `applies → JS-concerns → policy → mdc-refs`.
89
- 5. Повертається `Promise<number>` — `0` або `1`.
90
- 6. Оркестратор агрегує коди повернення з усіх правил.
91
-
92
- ### Сценарій 2. Standalone CLI-запуск
93
-
94
- Команда:
95
-
96
- ```bash
97
- bun npm/rules/flow/fix.mjs
98
- ```
99
-
100
- (еквівалентно `npx @nitra/cursor fix flow`).
101
-
102
- 1. Інтерпретатор виконує модуль як стартовий файл процесу.
103
- 2. Рядок `if (isRunAsCli(import.meta.url))` повертає `true`.
104
- 3. Викликається `await runRuleCli(import.meta.dirname)`:
105
- - Раннер парсить CLI-аргументи (флаги, шляхи).
106
- - Готує `ctx` для одиничного запуску.
107
- - Викликає `run` цього ж модуля (або еквівалентний внутрішній механізм через `runStandardRule`).
108
- - Форматує вивід (звіт про знахідки, кольори, summary).
109
- 4. `process.exit(<код>)` — процес завершується з кодом раннера: `0` за успіху, `1` за порушень.
110
-
111
- ### Сценарій 3. Імпорт у тестах / скриптах
112
-
113
- Можливий імпорт `run` для unit-/інтеграційного тестування або для написання кастомних оркестраторів:
114
-
115
- ```js
116
- import { run } from '@nitra/cursor/rules/flow/fix.mjs'
117
-
118
- const code = await run() // або await run(customCtx)
119
- if (code !== 0) {
120
- // обробити порушення
121
- }
122
- ```
123
-
124
- У цьому випадку top-level блок `if (isRunAsCli(...))` **не спрацьовує** (файл імпортовано, а не запущено напряму), отже `process.exit` не викликається — побічних ефектів на процес немає.
125
-
126
- ### Контракт повернення
127
-
128
- | Код | Значення | Дія викликача |
129
- | --- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------ |
130
- | `0` | Правило застосоване й порушень немає; або правило не застосовне (etap `applies` повернув false). | Продовжувати конвеєр. |
131
- | `1` | Знайдено порушення (зазвичай — невідповідність `.mdc`-файлу контракту). | CI має зафейлити збірку; IDE — показати помилку. |
132
-
133
- ## Особливості реалізації / Нотатки
134
-
135
- - **Pure-doc rule.** За коментарем у JSDoc функції `run`, правило `flow` не має програмних concern-ів. Це означає, що каталог `npm/rules/flow/` навмисно не містить (або містить лише мінімум) перевірок поверх JS-коду — основний контракт описано в `.mdc`-файлі, який і валідується через підетап `mdc-refs`.
136
- - **Стандартизований шаблон.** Файл написаний за єдиним шаблоном для всіх правил пакета: тонкий wrapper над `runStandardRule` + опційний standalone CLI-блок. Це спрощує генерацію/підтримку правил і робить їх взаємозамінними для оркестратора.
137
- - **`import.meta.dirname`** використовується замість `__dirname` (який недоступний в ES-модулях) і передається у `runStandardRule`/`runRuleCli` як локалізатор каталогу правила.
138
- - **Top-level `await`** у блоці `process.exit(await runRuleCli(...))` дозволений, тому що модуль є ES-модулем (`.mjs`).
139
- - **ESLint-disable з обґрунтуванням.** Вимкнення `n/no-process-exit` та `unicorn/no-process-exit` зроблено з явним коментарем-обґрунтуванням після `--`, що відповідає внутрішнім js-lint правилам пакета (вимога описувати причину disable).
140
-
141
- ## Rebuild Test (контрольна реконструкція)
142
-
143
- Спираючись виключно на цю документацію, файл `fix.mjs` можна відновити так:
144
-
145
- 1. Створити ES-модуль `npm/rules/flow/fix.mjs`.
146
- 2. Імпортувати з `../../scripts/lib/run-rule-cli.mjs` іменовані експорти `isRunAsCli` та `runRuleCli`.
147
- 3. Імпортувати з `../../scripts/lib/run-standard-rule.mjs` іменований експорт `runStandardRule`.
148
- 4. Експортувати функцію `run(ctx)`, яка повертає результат виклику `runStandardRule(import.meta.dirname, ctx)`. Контракт: `Promise<number>` (`0` — OK, `1` — порушення). JSDoc описує пайплайн `applies → JS-concerns → policy → mdc-refs` та зазначає, що правило pure-doc.
149
- 5. Додати top-level блок: `if (isRunAsCli(import.meta.url)) process.exit(await runRuleCli(import.meta.dirname))`.
150
- 6. Поряд із `process.exit` поставити `eslint-disable-next-line` для `n/no-process-exit` та `unicorn/no-process-exit` з обґрунтуванням, що standalone entry-point має повертати exit-code для CI/IDE.
151
-
152
- Такий файл буде функціонально еквівалентним оригіналу.
@@ -1,18 +0,0 @@
1
- import { isRunAsCli, runRuleCli } from '../../scripts/lib/run-rule-cli.mjs'
2
- import { runStandardRule } from '../../scripts/lib/run-standard-rule.mjs'
3
-
4
- /**
5
- * Запускає правило: applies → JS-concerns → policy → mdc-refs (через runStandardRule).
6
- * Pure-doc contract-правило: програмних concern-ів немає, тож по суті валідує `.mdc`.
7
- * @param {import('../../scripts/lib/run-standard-rule.mjs').RuleContext} [ctx] контекст прогону (walkCache тощо)
8
- * @returns {Promise<number>} 0 — OK, 1 — порушення
9
- */
10
- export function run(ctx) {
11
- return runStandardRule(import.meta.dirname, ctx)
12
- }
13
-
14
- if (isRunAsCli(import.meta.url)) {
15
- // Standalone: bun rules/flow/fix.mjs — повний еквівалент `npx @nitra/cursor fix flow`.
16
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
- process.exit(await runRuleCli(import.meta.dirname))
18
- }
@@ -1,127 +0,0 @@
1
- ---
2
- description: Контракт Пасивного Турнікета n-cursor flow — IDE-агент сам пише код, але ізолює/планує/перевіряє/релізить через flow init/spec/plan/verify/release.
3
- globs:
4
- alwaysApply: true
5
- ---
6
-
7
- # n-cursor flow — Пасивний Турнікет (контракт виконавця)
8
-
9
- `n-cursor flow` — це Dual-Mode Dispatcher. В інтерактивному середовищі (Cursor
10
- Composer, Claude Code) працює **Пасивний Турнікет**: ти, агент, **сам пишеш
11
- код**, а `n-cursor` лише ізолює роботу, **судить** її якість і релізить. Жодного
12
- прихованого спавну субагентів — керуєш ти.
13
-
14
- ## Контракт (виконуй у цьому порядку)
15
-
16
- 1. **Старт** — на початку задачі:
17
-
18
- ```
19
- npx @nitra/cursor flow init <branch> "<опис>"
20
- ```
21
-
22
- Створює ізольований worktree (`.worktrees/<branch>/`) і стан задачі. Якщо ти
23
- вже в worktree — новий не вкладається. `init` визначає **рівень** задачі
24
- (L0 тривіальне … L3 архітектурне) і **ризик** (low/med/high — за описом:
25
- security/auth/secret → high) за описом: для L0 (fix/typo/bump) фази Spec/План
26
- можна пропустити; для L≥1 вони рекомендовані. Рівень **і ризик** разом керують
27
- глибиною та фокусом `review`. Ризик можна уточнити полем `risk:` у frontmatter
28
- spec — `flow spec` його підхопить.
29
-
30
- 2. **Spec (дизайн)** — рекомендовано, не блокує. Brainstorm нашими термінами
31
- (НЕ викликаючи superpowers):
32
-
33
- - **human↔agent (дефолт):** питання по одному (перевага multiple-choice) →
34
- 2-3 підходи з рекомендацією → дизайн секціями з апрувом людини;
35
- - **agent↔agent:** `npx @nitra/cursor flow spec --panel` — панель персон
36
- (architect/skeptic/tester) → суддя-синтез; презентуй синтез людині.
37
-
38
- **Поглиблення (advanced elicitation, за потреби).** Якщо чернетка дизайну
39
- «сира» чи є непевність — запропонуй людині одну з технік (по одній за раз),
40
- застосуй обрану, повтори до «досить»:
41
-
42
- - **Expand / Contract** — розгорнути деталь, що бракує, або згорнути зайве до суті;
43
- - **Critique & Refine** — самокритика чернетки (слабкі місця, припущення), тоді доопрацювання;
44
- - **Identify Risks** — явно перелічити ризики/граничні випадки (наповнює поле `risk:` у frontmatter);
45
- - **Tree-of-Thoughts** — кілька гілок рішення паралельно, обрати найкращу з обґрунтуванням;
46
- - **Stakeholder Roundtable** — пройтись поглядами ролей (user / ops / security / maintainer);
47
- - **Self-Consistency** — 2-3 незалежні версії відповіді, звірити на суперечності.
48
-
49
- Що застосовано — фіксуй коротко в секції `## Elicitation History` спеки
50
- (traceability рішень).
51
-
52
- Збережи дизайн → `docs/specs/<date>-<slug>.md` (`kind: nitra-spec`,
53
- `plan: null`), тоді зафіксуй:
54
-
55
- ```
56
- npx @nitra/cursor flow spec
57
- ```
58
-
59
- 3. **План** — декомпозиція дизайну в кроки:
60
-
61
- - збережи `docs/plans/<date>-<slug>.md` (`kind: nitra-plan`, `spec:` → лінк на
62
- spec, `flow:` → шлях `.flow.json`; секція `## Кроки` — нумерований список
63
- `N. <task> — acceptance: <критерій>`);
64
- - зафіксуй (`--panel` — для agent↔agent синтезу кроків):
65
-
66
- ```
67
- npx @nitra/cursor flow plan
68
- ```
69
-
70
- Команда дзеркалить кроки у `.flow.json` (`status: planned`) і запускає `trace`
71
- для перевірки ланцюга spec↔plan↔flow. `verify` без плану лише попередить.
72
-
73
- 4. **Пиши код** сам, кроками. TDD: спершу падаючі тести, тоді реалізація.
74
-
75
- 5. **Перевіряй** після кожного логічного кроку:
76
-
77
- ```
78
- npx @nitra/cursor flow verify
79
- ```
80
-
81
- Проганяє Quality Gates (lint + coverage). Повертає `0` (pass) або `1` із
82
- виводом проваленого gate. **Обидва гейти перевіряють лише змінені файли**
83
- (diff від `base_commit`): `lint` — quick-режим, `coverage --changed` — vitest
84
- `--changed` + Stryker `--mutate` по diff. Працює однаково, незалежно від того,
85
- чи зміни вже закомічені у worktree. Повний coverage (увесь проєкт) — окремо:
86
- `bun run coverage` або `/n-coverage-fix`.
87
-
88
- 6. **Review (adversarial)** — рекомендовано перед release:
89
-
90
- ```
91
- npx @nitra/cursor flow review
92
- ```
93
-
94
- Незалежний субагент читає ЛИШЕ `git diff` від `base_commit` і шукає логічні
95
- баги/ризики, яких не ловлять механічні гейти. Кількість рецензентів —
96
- `max(рівень, ризик)` (L0→1 … L3→3; high-risk → 3, з безпековим фокусом
97
- промпта). Findings пишуться у `.flow.json` (не блокує); high-severity варто
98
- виправити перед фінішем.
99
-
100
- 7. **На провал** — виправ код за виводом і виклич `flow verify` знову. Максимум
101
- **3 спроби**; якщо не вдається — зупинись і поклич людину.
102
-
103
- 8. **Gate (вердикт готовності)** — перед фінішем:
104
-
105
- ```
106
- npx @nitra/cursor flow gate
107
- ```
108
-
109
- Синтезує verify-гейти і review-findings у `PASS / CONCERNS / FAIL` + score +
110
- причини (пише у `.flow.json`). `FAIL` (провалений gate або high-severity
111
- finding) → код 1; `release` на FAIL лише попередить (рішення за тобою).
112
-
113
- 9. **Фініш** — після зеленого `verify` і бажано `gate` ≠ FAIL:
114
-
115
- ```
116
- npx @nitra/cursor flow release --bump <patch|minor|major> --section <Added|Changed|Fixed> --message "<що зроблено>"
117
- ```
118
-
119
- Генерує `.changes/` і пише completion snapshot. Гілка готова до merge.
120
-
121
- ## Чого не роби
122
-
123
- - Не обходь `verify` — це єдиний критерій «готово».
124
- - Не редагуй `version` чи `CHANGELOG.md` вручну (це робить CI з `.changes/`).
125
- - Не коміть стан `.worktrees/<branch>.flow.json` у гілку — він поза git.
126
- - Не лінкуй spec↔plan неконсистентно — тримай `spec.plan`/`plan.spec`/`plan.flow`
127
- у front-matter; `flow spec`/`flow plan` перевіряють їх через `trace`.
@@ -1 +0,0 @@
1
- { "auto": "завжди" }