@nitra/cursor 4.0.0 → 4.1.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 (132) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/n-cursor.js +25 -13
  3. package/lib/models.mjs +67 -0
  4. package/package.json +2 -1
  5. package/rules/abie/fix.mjs +1 -1
  6. package/rules/bun/docs/fix.md +3 -0
  7. package/rules/bun/fix.mjs +1 -1
  8. package/rules/capacitor/fix.mjs +1 -1
  9. package/rules/changelog/docs/fix.md +3 -0
  10. package/rules/changelog/fix.mjs +1 -1
  11. package/rules/ci4/fix.mjs +1 -1
  12. package/rules/ci4/js/docs/marksman_config.md +1 -0
  13. package/rules/docker/docs/fix.md +1 -1
  14. package/rules/docker/fix.mjs +1 -1
  15. package/rules/docker/lint/docs/lint.md +1 -0
  16. package/rules/efes/docs/fix.md +2 -1
  17. package/rules/efes/fix.mjs +1 -1
  18. package/rules/feedback/fix.mjs +1 -1
  19. package/rules/ga/fix.mjs +1 -1
  20. package/rules/ga/js/lint.mjs +1 -1
  21. package/rules/graphql/docs/fix.md +4 -1
  22. package/rules/graphql/fix.mjs +1 -1
  23. package/rules/graphql/lib/docs/graphql-gql-scan.md +3 -0
  24. package/rules/hasura/fix.mjs +1 -1
  25. package/rules/image-avif/docs/fix.md +4 -1
  26. package/rules/image-avif/fix.mjs +1 -1
  27. package/rules/image-avif/js/docs/avif_generation.md +1 -0
  28. package/rules/image-compress/fix.mjs +1 -1
  29. package/rules/js-bun-db/fix.mjs +1 -1
  30. package/rules/js-bun-db/lib/docs/bun-sql-scan.md +6 -0
  31. package/rules/js-bun-redis/fix.mjs +1 -1
  32. package/rules/js-lint/fix.mjs +1 -1
  33. package/rules/js-lint/js/docs/utils_imports.md +1 -0
  34. package/rules/js-lint-ci/docs/fix.md +4 -1
  35. package/rules/js-lint-ci/fix.mjs +1 -1
  36. package/rules/js-mssql/docs/fix.md +3 -0
  37. package/rules/js-mssql/fix.mjs +1 -1
  38. package/rules/js-mssql/lib/docs/mssql-pool-scan.md +9 -0
  39. package/rules/js-run/docs/fix.md +3 -0
  40. package/rules/js-run/fix.mjs +1 -1
  41. package/rules/js-run/lib/docs/check-env-scan.md +2 -1
  42. package/rules/js-run/lib/docs/promise-settimeout-scan.md +4 -0
  43. package/rules/k8s/docs/fix.md +3 -0
  44. package/rules/k8s/fix.mjs +1 -1
  45. package/rules/nginx-default-tpl/docs/fix.md +3 -0
  46. package/rules/nginx-default-tpl/fix.mjs +1 -1
  47. package/rules/npm-module/fix.mjs +1 -1
  48. package/rules/npm-module/js/header_doc_pointer.mjs +14 -3
  49. package/rules/php/docs/fix.md +2 -1
  50. package/rules/php/fix.mjs +1 -1
  51. package/rules/python/docs/fix.md +4 -1
  52. package/rules/python/fix.mjs +1 -1
  53. package/rules/rego/fix.mjs +1 -1
  54. package/rules/rego/js/lint.mjs +1 -1
  55. package/rules/release/docs/fix.md +4 -1
  56. package/rules/release/fix.mjs +1 -1
  57. package/rules/rust/fix.mjs +1 -1
  58. package/rules/security/docs/fix.md +1 -1
  59. package/rules/security/fix.mjs +1 -1
  60. package/rules/style-lint/docs/fix.md +3 -0
  61. package/rules/style-lint/fix.mjs +1 -1
  62. package/rules/tauri/docs/fix.md +4 -1
  63. package/rules/tauri/fix.mjs +1 -1
  64. package/rules/test/docs/fix.md +3 -0
  65. package/rules/test/fix.mjs +1 -1
  66. package/rules/test/js/no-relative-fs-path.mjs +2 -1
  67. package/rules/text/docs/fix.md +3 -0
  68. package/rules/text/fix.mjs +1 -1
  69. package/rules/text/js/lint.mjs +1 -1
  70. package/rules/vue/fix.mjs +1 -1
  71. package/rules/worktree/fix.mjs +1 -1
  72. package/scripts/auto-rules.mjs +1 -1
  73. package/scripts/coverage-classify/index.mjs +10 -10
  74. package/scripts/coverage-fix.mjs +2 -2
  75. package/scripts/dispatcher/graph/lib/cmd-init.mjs +112 -0
  76. package/scripts/dispatcher/graph/lib/cmd-invalidate.mjs +96 -0
  77. package/scripts/dispatcher/graph/lib/cmd-kill.mjs +141 -0
  78. package/scripts/dispatcher/graph/lib/cmd-plan.mjs +142 -0
  79. package/scripts/dispatcher/graph/lib/cmd-run.mjs +328 -0
  80. package/scripts/dispatcher/graph/lib/cmd-scan.mjs +115 -0
  81. package/scripts/dispatcher/graph/lib/cmd-setup.mjs +111 -0
  82. package/scripts/dispatcher/graph/lib/cmd-signals.mjs +328 -0
  83. package/scripts/dispatcher/graph/lib/cmd-status.mjs +131 -0
  84. package/scripts/dispatcher/graph/lib/cmd-verify.mjs +100 -0
  85. package/scripts/dispatcher/graph/lib/cmd-watch.mjs +128 -0
  86. package/scripts/dispatcher/graph/lib/config.mjs +103 -0
  87. package/scripts/dispatcher/graph/lib/frontmatter.mjs +224 -0
  88. package/scripts/dispatcher/graph/lib/nnn.mjs +127 -0
  89. package/scripts/dispatcher/graph/lib/node-state.mjs +157 -0
  90. package/scripts/dispatcher/graph/lib/scanner.mjs +235 -0
  91. package/scripts/dispatcher/graph/lib/worktree-ops.mjs +193 -0
  92. package/scripts/dispatcher/graph-tasks.mjs +92 -0
  93. package/scripts/dispatcher/index.mjs +3 -3
  94. package/scripts/dispatcher/lib/docs/events.md +1 -0
  95. package/scripts/dispatcher/lib/executor.mjs +1 -1
  96. package/scripts/dispatcher/lib/subagent-runner.mjs +9 -9
  97. package/scripts/dispatcher/trace.mjs +6 -2
  98. package/scripts/docs/build-agents-commands.md +1 -0
  99. package/scripts/docs/cli-entry.md +6 -0
  100. package/scripts/graph/index.mjs +115 -0
  101. package/scripts/graph/lib/config.mjs +62 -0
  102. package/scripts/graph/lib/dag.mjs +161 -0
  103. package/scripts/graph/lib/frontmatter.mjs +70 -0
  104. package/scripts/graph/lib/nnn.mjs +77 -0
  105. package/scripts/graph/lib/state.mjs +110 -0
  106. package/scripts/graph/scan.mjs +64 -0
  107. package/scripts/graph/status.mjs +86 -0
  108. package/scripts/lib/docs/load-cursor-config.md +3 -0
  109. package/scripts/lib/root-notice.mjs +4 -2
  110. package/scripts/lib/rule-predicates.mjs +1 -1
  111. package/scripts/lib/worktree-notice.mjs +14 -7
  112. package/scripts/lib/worktree.mjs +3 -2
  113. package/scripts/utils/resolve-js-root.mjs +2 -1
  114. package/scripts/utils/with-lock.mjs +1 -1
  115. package/skills/docgen/js/docgen-batch.mjs +7 -7
  116. package/skills/docgen/js/docgen-extract.mjs +80 -37
  117. package/skills/docgen/js/docgen-ignore.mjs +1 -1
  118. package/skills/docgen/js/docgen-prompts.mjs +21 -5
  119. package/skills/fix/js/llm-worker.mjs +19 -22
  120. package/skills/fix/js/orchestrator.mjs +6 -7
  121. package/skills/fix/js/t0.mjs +14 -13
  122. package/types/bin/n-cursor.d.ts +1 -1
  123. package/rules/flow/docs/fix.md +0 -152
  124. package/rules/flow/fix.mjs +0 -18
  125. package/rules/flow/flow.mdc +0 -127
  126. package/rules/flow/meta.json +0 -1
  127. package/scripts/dispatcher/lib/docs/flow-lock.md +0 -161
  128. package/scripts/dispatcher/lib/docs/flow-resolve.md +0 -267
  129. package/scripts/dispatcher/lib/flow-plan.mjs +0 -153
  130. package/scripts/dispatcher/lib/flow-resolve.mjs +0 -156
  131. package/scripts/dispatcher/lib/flow-signals.mjs +0 -235
  132. package/scripts/dispatcher/lib/flow-verify.mjs +0 -127
@@ -26,6 +26,9 @@
26
26
  ### `run(ctx)`
27
27
 
28
28
  ```js
29
+ /**
30
+ *
31
+ */
29
32
  export function run(ctx) {
30
33
  return runStandardRule(import.meta.dirname, ctx)
31
34
  }
package/rules/k8s/fix.mjs CHANGED
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -22,6 +22,9 @@
22
22
  ### `run(ctx)`
23
23
 
24
24
  ```js
25
+ /**
26
+ *
27
+ */
25
28
  export function run(ctx) {
26
29
  return runStandardRule(import.meta.dirname, ctx)
27
30
  }
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -14,16 +14,27 @@ const MODULE_JSDOC_RE = /\/\*\*[\s\S]*?\*\//
14
14
  */
15
15
  const CODE_START_RE = /^(?:import|export)\b/m
16
16
 
17
- /** Кількість непорожніх рядків між `/**` і `*\/` (після зрізання `*`-відступу). */
17
+ const NON_WHITESPACE_RE = /\S/
18
+ const STAR_INDENT_RE = /^\s*\*\s?/
19
+
20
+ /**
21
+ * Кількість непорожніх рядків між `/**` і `*\/` (після зрізання `*`-відступу).
22
+ * @param {string} block повний текст JSDoc-блоку з обрамленням
23
+ * @returns {number} кількість непорожніх рядків у тілі
24
+ */
18
25
  function contentLineCount(block) {
19
26
  return block
20
27
  .split('\n')
21
28
  .slice(1, -1)
22
- .filter(l => /\S/.test(l.replace(/^\s*\*\s?/, '')))
29
+ .filter(l => NON_WHITESPACE_RE.test(l.replace(STAR_INDENT_RE, '')))
23
30
  .length
24
31
  }
25
32
 
26
- /** Повертає module-level JSDoc або `null`, якщо його немає. */
33
+ /**
34
+ * Повертає module-level JSDoc або `null`, якщо його немає.
35
+ * @param {string} source вміст mjs-файлу
36
+ * @returns {string|null} текст module-level JSDoc-блоку або null
37
+ */
27
38
  function moduleJsDoc(source) {
28
39
  const codeStart = CODE_START_RE.exec(source)
29
40
  const prefix = codeStart ? source.slice(0, codeStart.index) : source
@@ -58,6 +58,7 @@ export function run(ctx)
58
58
 
59
59
  ```js
60
60
  import { run } from '@nitra/cursor/rules/php/fix.mjs'
61
+
61
62
  const code = await run(sharedCtx)
62
63
  ```
63
64
 
@@ -81,7 +82,7 @@ const code = await run(sharedCtx)
81
82
  У standalone-гілці явно вимкнено два правила лінтера:
82
83
 
83
84
  ```js
84
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
85
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
85
86
  process.exit(await runRuleCli(import.meta.dirname))
86
87
  ```
87
88
 
package/rules/php/fix.mjs CHANGED
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -30,6 +30,9 @@ Public-функція правила, делегатор до `runStandardRule`.
30
30
  **Сигнатура**
31
31
 
32
32
  ```js
33
+ /**
34
+ *
35
+ */
33
36
  export function run(ctx) {
34
37
  return runStandardRule(import.meta.dirname, ctx)
35
38
  }
@@ -61,7 +64,7 @@ export function run(ctx) {
61
64
 
62
65
  ```js
63
66
  if (isRunAsCli(import.meta.url)) {
64
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
67
+ // eslint-disable-next-line n/no-process-exit
65
68
  process.exit(await runRuleCli(import.meta.dirname))
66
69
  }
67
70
  ```
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -7,6 +7,6 @@ import { runLintRego } from '../lint/lint.mjs'
7
7
  * @param {string[] | undefined} _files ігнорується (whole-repo аналіз)
8
8
  * @returns {Promise<number>} exit code
9
9
  */
10
- export async function lint(_files) {
10
+ export function lint(_files) {
11
11
  return runLintRego()
12
12
  }
@@ -22,6 +22,9 @@
22
22
  ### `run(ctx)`
23
23
 
24
24
  ```js
25
+ /**
26
+ *
27
+ */
25
28
  export function run(ctx) {
26
29
  return runStandardRule(import.meta.dirname, ctx)
27
30
  }
@@ -40,7 +43,7 @@ export function run(ctx) {
40
43
 
41
44
  ```js
42
45
  if (isRunAsCli(import.meta.url)) {
43
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
46
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
44
47
  process.exit(await runRuleCli(import.meta.dirname))
45
48
  }
46
49
  ```
@@ -12,6 +12,6 @@ export function run(ctx) {
12
12
  }
13
13
 
14
14
  if (isRunAsCli(import.meta.url)) {
15
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
15
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
16
16
  process.exit(await runRuleCli(import.meta.dirname))
17
17
  }
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -74,7 +74,7 @@ export function run(ctx)
74
74
  Рядок із `process.exit(await runRuleCli(...))` має локальну директиву:
75
75
 
76
76
  ```js
77
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
77
+
78
78
  ```
79
79
 
80
80
  Це свідома відмова від загальної заборони `process.exit`: для CLI entry-point потрібно повертати числовий код, інакше CI/IDE не зможуть розрізнити OK і порушення. Заборона залишається активною для решти кодової бази.
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -26,6 +26,9 @@
26
26
  ### `run(ctx)`
27
27
 
28
28
  ```js
29
+ /**
30
+ *
31
+ */
29
32
  export function run(ctx) {
30
33
  return runStandardRule(import.meta.dirname, ctx)
31
34
  }
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -33,6 +33,9 @@
33
33
  ### `run(ctx)`
34
34
 
35
35
  ```js
36
+ /**
37
+ *
38
+ */
36
39
  export function run(ctx) {
37
40
  return runStandardRule(import.meta.dirname, ctx)
38
41
  }
@@ -57,7 +60,7 @@ export function run(ctx) {
57
60
 
58
61
  ```js
59
62
  if (isRunAsCli(import.meta.url)) {
60
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
63
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
61
64
  process.exit(await runRuleCli(import.meta.dirname))
62
65
  }
63
66
  ```
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -32,6 +32,9 @@
32
32
  ### `run(ctx)`
33
33
 
34
34
  ```js
35
+ /**
36
+ *
37
+ */
35
38
  export function run(ctx) {
36
39
  return runStandardRule(import.meta.dirname, ctx)
37
40
  }
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -62,6 +62,7 @@ const FS_PATH_ARG_POSITIONS = new Map([
62
62
  * одного з них — це OK (тест свідомо передає absolute чи URL).
63
63
  */
64
64
  const ABSOLUTE_PREFIXES = ['/', '\\', 'file:', 'http:', 'https:', 'data:']
65
+ const WINDOWS_DRIVE_RE = /^[A-Za-z]:[\\/]/u
65
66
 
66
67
  /**
67
68
  * Чи string literal — relative path (тобто баг). Перевіряє лише string-літерали
@@ -95,7 +96,7 @@ function isRelativeString(s) {
95
96
  if (s.startsWith(prefix)) return false
96
97
  }
97
98
  // Windows drive letter, наприклад `C:\foo` або `C:/foo`.
98
- if (/^[A-Za-z]:[\\/]/u.test(s)) return false
99
+ if (WINDOWS_DRIVE_RE.test(s)) return false
99
100
  return true
100
101
  }
101
102
 
@@ -24,6 +24,9 @@
24
24
  ### `run(ctx)`
25
25
 
26
26
  ```js
27
+ /**
28
+ *
29
+ */
27
30
  export function run(ctx) {
28
31
  return runStandardRule(import.meta.dirname, ctx)
29
32
  }
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -7,6 +7,6 @@ import { runLintTextCli } from '../lint/lint.mjs'
7
7
  * @param {string[] | undefined} _files ігнорується (whole-repo аналіз)
8
8
  * @returns {Promise<number>} exit code
9
9
  */
10
- export async function lint(_files) {
10
+ export function lint(_files) {
11
11
  return runLintTextCli()
12
12
  }
package/rules/vue/fix.mjs CHANGED
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -14,6 +14,6 @@ export function run(ctx) {
14
14
  if (isRunAsCli(import.meta.url)) {
15
15
  // Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
16
16
  // (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
17
- // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
17
+ // eslint-disable-next-line n/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
18
18
  process.exit(await runRuleCli(import.meta.dirname))
19
19
  }
@@ -332,7 +332,7 @@ function resolveRuleDependencies(detectedRules, addRule) {
332
332
  * @param {{root:string, facts:object, paths:string[], packageJsonParsed:unknown}} ctx контекст
333
333
  * @returns {Promise<boolean>} true, якщо правило активне
334
334
  */
335
- async function specMatches(spec, ctx) {
335
+ function specMatches(spec, ctx) {
336
336
  if ('always' in spec) return true
337
337
  if ('glob' in spec) {
338
338
  const res = spec.glob.map(g => globToRegex(g))
@@ -23,9 +23,9 @@ const FALLBACK_VERDICT = {
23
23
 
24
24
  /**
25
25
  * Викликає pi і повертає raw stdout.
26
- * @param {string} prompt
26
+ * @param {string} prompt текст промпта
27
27
  * @param {string} model provider/model-id або '' для pi-дефолту
28
- * @returns {string}
28
+ * @returns {string} stdout pi-процесу
29
29
  * @throws якщо pi не знайдено або повертає ненульовий exit code
30
30
  */
31
31
  function callPi(prompt, model) {
@@ -41,11 +41,11 @@ function callPi(prompt, model) {
41
41
 
42
42
  /**
43
43
  * Два тири: LOCAL_MIN → Tier 2 CLOUD_MIN → FALLBACK_VERDICT.
44
- * @param {{file: string, mutants: object[]}} group
45
- * @param {object} mutant
46
- * @param {string} cwd
44
+ * @param {{file: string, mutants: object[]}} group група мутантів одного файлу
45
+ * @param {object} mutant конкретний мутант
46
+ * @param {string} cwd корінь проєкту
47
47
  * @param {(prompt: string, model: string) => string} callPiFn ін'єкція для тестів
48
- * @returns {object} verdict
48
+ * @returns {object} verdict класифікації
49
49
  */
50
50
  function classifyOne(group, mutant, cwd, callPiFn) {
51
51
  const prompt = `${SYSTEM_PROMPT}\n\n${buildUserPrompt({ ...mutant, file: group.file }, cwd)}`
@@ -60,8 +60,8 @@ function classifyOne(group, mutant, cwd, callPiFn) {
60
60
  try {
61
61
  const text = callPiFn(prompt, CLOUD_MIN)
62
62
  return parseVerdict(text)
63
- } catch (e) {
64
- console.warn(`⚠ coverage classify: ${loc} both tiers failed: ${e.message}`)
63
+ } catch (error) {
64
+ console.warn(`⚠ coverage classify: ${loc} both tiers failed: ${error.message}`)
65
65
  return { ...FALLBACK_VERDICT }
66
66
  }
67
67
  }
@@ -69,12 +69,12 @@ function classifyOne(group, mutant, cwd, callPiFn) {
69
69
 
70
70
  /**
71
71
  * Класифікує survived мутантів через pi (LOCAL_MIN → CLOUD_MIN → fallback).
72
- * @param {Array<{file: string, mutants: object[], exampleTest?: object|null, recommendationText?: string|null}>} survived
72
+ * @param {Array<{file: string, mutants: object[], exampleTest?: object|null, recommendationText?: string|null}>} survived список вцілілих мутантів
73
73
  * @param {string} cwd корінь проєкту
74
74
  * @param {{cachePath?: string, callPi?: Function}} [opts] ін'єкції для тестів
75
75
  * @returns {Promise<Array<{key: string, verdict: object}>>} verdicts
76
76
  */
77
- export async function classify(survived, cwd, opts = {}) {
77
+ export function classify(survived, cwd, opts = {}) {
78
78
  const cachePath = opts.cachePath ?? join(cwd, 'npm/reports/coverage-classify.cache.json')
79
79
  const callPiFn = opts.callPi ?? callPi
80
80
  const cacheModel = `${resolveModel('min') || 'default'}+${CLOUD_MIN || 'cloud'}`
@@ -42,9 +42,9 @@ export async function fixSurvivedMutants(survived, projectRoot, opts = {}) {
42
42
 
43
43
  /**
44
44
  * Викликає pi в агентному режимі з live-output до stdout.
45
- * @param {string} prompt
45
+ * @param {string} prompt текст промпта
46
46
  * @param {string} model provider/model-id або '' для pi-дефолту
47
- * @param {{ cwd?: string }} [piOpts]
47
+ * @param {{ cwd?: string }} [piOpts] опційні параметри (cwd)
48
48
  */
49
49
  function callPi(prompt, model, { cwd } = {}) {
50
50
  const modelArgs = model ? ['--model', model] : []
@@ -0,0 +1,112 @@
1
+ /**
2
+ * `n-cursor graph init <name>` — створює task.md шаблон для нового вузла.
3
+ *
4
+ * Не потребує LLM. Просто пише task.md з front-matter і порожнім тілом.
5
+ * Ім'я може містити `/` для вкладених вузлів (напр. "research/collect-data").
6
+ *
7
+ * FS ін'єктується для тестованості.
8
+ */
9
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
10
+ import { join } from 'node:path'
11
+ import { cwd as processCwd } from 'node:process'
12
+
13
+ import { buildMarkdown } from './frontmatter.mjs'
14
+ import { loadConfig, resolveTasksDir } from './config.mjs'
15
+
16
+ /**
17
+ * Будує front-matter для task.md шаблону.
18
+ * @param {{ now: string, name: string }} params параметри
19
+ * @returns {Record<string, unknown>} front-matter об'єкт
20
+ */
21
+ export function buildTaskFrontMatter(params) {
22
+ return {
23
+ created_at: params.now,
24
+ budget_sec: 600,
25
+ mode: 'human',
26
+ interactive: true,
27
+ executor: {
28
+ type: 'agent',
29
+ model_tier: 'AVG',
30
+ skills: ['bash', 'write-files']
31
+ },
32
+ hint: 'atomic',
33
+ deps: []
34
+ }
35
+ }
36
+
37
+ /**
38
+ * `graph init <name>` command handler.
39
+ * @param {string[]} args аргументи: [name]
40
+ * @param {{
41
+ * cwd?: string,
42
+ * log?: (m: string) => void,
43
+ * writeFile?: (p: string, c: string, enc: string) => void,
44
+ * exists?: (p: string) => boolean,
45
+ * mkdir?: (p: string, opts?: object) => void,
46
+ * now?: () => string,
47
+ * readFile?: (p: string, enc: string) => string
48
+ * }} [deps] ін'єкції
49
+ * @returns {Promise<number>} exit code
50
+ */
51
+ export async function cmdInit(args, deps = {}) {
52
+ const root = deps.cwd ?? processCwd()
53
+ const log = deps.log ?? console.log
54
+ const writeFile = deps.writeFile ?? ((p, c, enc) => writeFileSync(p, c, enc))
55
+ const exists = deps.exists ?? existsSync
56
+ const mkdir = deps.mkdir ?? ((p, opts) => mkdirSync(p, opts))
57
+ const nowFn = deps.now ?? (() => new Date().toISOString())
58
+
59
+ const [name] = args
60
+ if (!name) {
61
+ log('Usage: n-cursor graph init <name>')
62
+ log(' name може містити / для вкладених вузлів (напр. "research/collect-data")')
63
+ return 1
64
+ }
65
+
66
+ const config = loadConfig({ root, readFile: deps.readFile, exists })
67
+ const tasksDir = resolveTasksDir(config, root)
68
+
69
+ const nodeDir = join(tasksDir, name)
70
+ const taskPath = join(nodeDir, 'task.md')
71
+
72
+ if (exists(taskPath)) {
73
+ log(`init: ${taskPath} вже існує — пропускаємо`)
74
+ return 0
75
+ }
76
+
77
+ // Створюємо директорію рекурсивно
78
+ try {
79
+ mkdir(nodeDir, { recursive: true })
80
+ } catch (err) {
81
+ log(`init: не вдалося створити директорію ${nodeDir} — ${err.message ?? String(err)}`)
82
+ return 1
83
+ }
84
+
85
+ const fm = buildTaskFrontMatter({ now: nowFn(), name })
86
+ const body = [
87
+ `## Mission`,
88
+ ``,
89
+ `<!-- Опишіть завдання вузла тут -->`,
90
+ ``,
91
+ `## Done when`,
92
+ ``,
93
+ `<!-- Критерії успіху -->`,
94
+ ``,
95
+ `## Context`,
96
+ ``,
97
+ `<!-- Додатковий контекст для виконавця -->`,
98
+ ``
99
+ ].join('\n')
100
+
101
+ const content = buildMarkdown(fm, body)
102
+
103
+ try {
104
+ writeFile(taskPath, content, 'utf8')
105
+ log(`init: створено ${taskPath}`)
106
+ } catch (err) {
107
+ log(`init: не вдалося записати ${taskPath} — ${err.message ?? String(err)}`)
108
+ return 1
109
+ }
110
+
111
+ return 0
112
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `n-cursor graph invalidate <path> [--no-cascade]` — позначає вузол як invalidated.
3
+ *
4
+ * Записує порожній файл `invalidated` у директорію вузла.
5
+ * За замовчуванням каскадно інвалідує всі залежні вузли.
6
+ * --no-cascade — лише поточний вузол.
7
+ *
8
+ * FS ін'єктується для тестованості.
9
+ */
10
+ import { execSync } from 'node:child_process'
11
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
12
+ import { join } from 'node:path'
13
+ import { cwd as processCwd } from 'node:process'
14
+
15
+ import { loadConfig, resolveTasksDir } from './config.mjs'
16
+ import { scanNodes } from './scanner.mjs'
17
+ import { listActiveWorktrees } from './worktree-ops.mjs'
18
+
19
+ /**
20
+ * `graph invalidate <path> [--no-cascade]` command handler.
21
+ * @param {string[]} args аргументи
22
+ * @param {{
23
+ * cwd?: string,
24
+ * log?: (m: string) => void,
25
+ * readFile?: (p: string, enc: string) => string,
26
+ * writeFile?: (p: string, c: string, enc: string) => void,
27
+ * readdir?: (d: string) => string[],
28
+ * exists?: (p: string) => boolean,
29
+ * execSync?: (cmd: string, opts?: object) => string
30
+ * }} [deps] ін'єкції
31
+ * @returns {Promise<number>} exit code
32
+ */
33
+ export async function cmdInvalidate(args, deps = {}) {
34
+ const root = deps.cwd ?? processCwd()
35
+ const log = deps.log ?? console.log
36
+ const readFile = deps.readFile ?? ((p, enc) => readFileSync(p, enc))
37
+ const writeFile = deps.writeFile ?? ((p, c, enc) => writeFileSync(p, c, enc))
38
+ const readdir = deps.readdir ?? (d => (existsSync(d) ? readdirSync(d) : []))
39
+ const exists = deps.exists ?? existsSync
40
+ const execSyncFn = deps.execSync ?? ((cmd, o) => execSync(cmd, { ...o, encoding: 'utf8' }))
41
+
42
+ let nodePath = null
43
+ let noCascade = false
44
+
45
+ for (const arg of args) {
46
+ if (arg === '--no-cascade') noCascade = true
47
+ else if (!arg.startsWith('-')) nodePath = arg
48
+ }
49
+
50
+ if (!nodePath) {
51
+ log('Usage: n-cursor graph invalidate <path> [--no-cascade]')
52
+ return 1
53
+ }
54
+
55
+ const config = loadConfig({ root, readFile, exists })
56
+ const tasksDir = resolveTasksDir(config, root)
57
+ const nodeDir = join(tasksDir, nodePath)
58
+
59
+ if (!exists(join(nodeDir, 'task.md'))) {
60
+ log(`invalidate: вузол "${nodePath}" не знайдено`)
61
+ return 1
62
+ }
63
+
64
+ // Записуємо invalidated sentinel
65
+ try {
66
+ writeFile(join(nodeDir, 'invalidated'), '', 'utf8')
67
+ log(`invalidate: вузол "${nodePath}" інвалідовано`)
68
+ } catch (err) {
69
+ log(`invalidate: не вдалося записати invalidated — ${err.message ?? String(err)}`)
70
+ return 1
71
+ }
72
+
73
+ if (noCascade) return 0
74
+
75
+ // Каскадна інвалідація
76
+ const activeWorktrees = listActiveWorktrees(root, { execSync: execSyncFn })
77
+ const allNodes = scanNodes(tasksDir, activeWorktrees, {
78
+ readdirSync: readdir,
79
+ existsSync: exists,
80
+ readFileSync: readFile
81
+ })
82
+
83
+ const dependents = allNodes.filter(n => n.deps.includes(nodePath))
84
+ for (const dep of dependents) {
85
+ if (!exists(join(dep.dir, 'invalidated'))) {
86
+ try {
87
+ writeFile(join(dep.dir, 'invalidated'), '', 'utf8')
88
+ log(`invalidate: каскадна інвалідація "${dep.path}"`)
89
+ } catch {
90
+ // пропускаємо
91
+ }
92
+ }
93
+ }
94
+
95
+ return 0
96
+ }