@nitra/cursor 4.1.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.
- package/CHANGELOG.md +6 -0
- package/bin/n-cursor.js +25 -13
- package/lib/models.mjs +1 -2
- package/package.json +1 -1
- package/rules/abie/fix.mjs +1 -1
- package/rules/bun/docs/fix.md +3 -0
- package/rules/bun/fix.mjs +1 -1
- package/rules/capacitor/fix.mjs +1 -1
- package/rules/changelog/docs/fix.md +3 -0
- package/rules/changelog/fix.mjs +1 -1
- package/rules/ci4/fix.mjs +1 -1
- package/rules/ci4/js/docs/marksman_config.md +1 -0
- package/rules/docker/docs/fix.md +1 -1
- package/rules/docker/fix.mjs +1 -1
- package/rules/docker/lint/docs/lint.md +1 -0
- package/rules/efes/docs/fix.md +2 -1
- package/rules/efes/fix.mjs +1 -1
- package/rules/feedback/fix.mjs +1 -1
- package/rules/ga/fix.mjs +1 -1
- package/rules/ga/js/lint.mjs +1 -1
- package/rules/graphql/docs/fix.md +4 -1
- package/rules/graphql/fix.mjs +1 -1
- package/rules/graphql/lib/docs/graphql-gql-scan.md +3 -0
- package/rules/hasura/fix.mjs +1 -1
- package/rules/image-avif/docs/fix.md +4 -1
- package/rules/image-avif/fix.mjs +1 -1
- package/rules/image-avif/js/docs/avif_generation.md +1 -0
- package/rules/image-compress/fix.mjs +1 -1
- package/rules/js-bun-db/fix.mjs +1 -1
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +6 -0
- package/rules/js-bun-redis/fix.mjs +1 -1
- package/rules/js-lint/fix.mjs +1 -1
- package/rules/js-lint/js/docs/utils_imports.md +1 -0
- package/rules/js-lint-ci/docs/fix.md +4 -1
- package/rules/js-lint-ci/fix.mjs +1 -1
- package/rules/js-mssql/docs/fix.md +3 -0
- package/rules/js-mssql/fix.mjs +1 -1
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +9 -0
- package/rules/js-run/docs/fix.md +3 -0
- package/rules/js-run/fix.mjs +1 -1
- package/rules/js-run/lib/docs/check-env-scan.md +2 -1
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +4 -0
- package/rules/k8s/docs/fix.md +3 -0
- package/rules/k8s/fix.mjs +1 -1
- package/rules/nginx-default-tpl/docs/fix.md +3 -0
- package/rules/nginx-default-tpl/fix.mjs +1 -1
- package/rules/npm-module/fix.mjs +1 -1
- package/rules/npm-module/js/header_doc_pointer.mjs +14 -3
- package/rules/php/docs/fix.md +2 -1
- package/rules/php/fix.mjs +1 -1
- package/rules/python/docs/fix.md +4 -1
- package/rules/python/fix.mjs +1 -1
- package/rules/rego/fix.mjs +1 -1
- package/rules/rego/js/lint.mjs +1 -1
- package/rules/release/docs/fix.md +4 -1
- package/rules/release/fix.mjs +1 -1
- package/rules/rust/fix.mjs +1 -1
- package/rules/security/docs/fix.md +1 -1
- package/rules/security/fix.mjs +1 -1
- package/rules/style-lint/docs/fix.md +3 -0
- package/rules/style-lint/fix.mjs +1 -1
- package/rules/tauri/docs/fix.md +4 -1
- package/rules/tauri/fix.mjs +1 -1
- package/rules/test/docs/fix.md +3 -0
- package/rules/test/fix.mjs +1 -1
- package/rules/test/js/no-relative-fs-path.mjs +2 -1
- package/rules/text/docs/fix.md +3 -0
- package/rules/text/fix.mjs +1 -1
- package/rules/text/js/lint.mjs +1 -1
- package/rules/vue/fix.mjs +1 -1
- package/rules/worktree/fix.mjs +1 -1
- package/scripts/auto-rules.mjs +1 -1
- package/scripts/coverage-classify/index.mjs +10 -10
- package/scripts/coverage-fix.mjs +2 -2
- package/scripts/dispatcher/graph/lib/cmd-init.mjs +112 -0
- package/scripts/dispatcher/graph/lib/cmd-invalidate.mjs +96 -0
- package/scripts/dispatcher/graph/lib/cmd-kill.mjs +141 -0
- package/scripts/dispatcher/graph/lib/cmd-plan.mjs +142 -0
- package/scripts/dispatcher/graph/lib/cmd-run.mjs +328 -0
- package/scripts/dispatcher/graph/lib/cmd-scan.mjs +115 -0
- package/scripts/dispatcher/graph/lib/cmd-setup.mjs +111 -0
- package/scripts/dispatcher/graph/lib/cmd-signals.mjs +328 -0
- package/scripts/dispatcher/graph/lib/cmd-status.mjs +131 -0
- package/scripts/dispatcher/graph/lib/cmd-verify.mjs +100 -0
- package/scripts/dispatcher/graph/lib/cmd-watch.mjs +128 -0
- package/scripts/dispatcher/graph/lib/config.mjs +103 -0
- package/scripts/dispatcher/graph/lib/frontmatter.mjs +224 -0
- package/scripts/dispatcher/graph/lib/nnn.mjs +127 -0
- package/scripts/dispatcher/graph/lib/node-state.mjs +157 -0
- package/scripts/dispatcher/graph/lib/scanner.mjs +235 -0
- package/scripts/dispatcher/graph/lib/worktree-ops.mjs +193 -0
- package/scripts/dispatcher/graph-tasks.mjs +92 -0
- package/scripts/dispatcher/index.mjs +3 -3
- package/scripts/dispatcher/lib/docs/events.md +1 -0
- package/scripts/dispatcher/lib/executor.mjs +1 -1
- package/scripts/dispatcher/lib/subagent-runner.mjs +9 -9
- package/scripts/dispatcher/trace.mjs +6 -2
- package/scripts/docs/build-agents-commands.md +1 -0
- package/scripts/docs/cli-entry.md +6 -0
- package/scripts/graph/index.mjs +115 -0
- package/scripts/graph/lib/config.mjs +62 -0
- package/scripts/graph/lib/dag.mjs +161 -0
- package/scripts/graph/lib/frontmatter.mjs +70 -0
- package/scripts/graph/lib/nnn.mjs +77 -0
- package/scripts/graph/lib/state.mjs +110 -0
- package/scripts/graph/scan.mjs +64 -0
- package/scripts/graph/status.mjs +86 -0
- package/scripts/lib/docs/load-cursor-config.md +3 -0
- package/scripts/lib/root-notice.mjs +4 -2
- package/scripts/lib/rule-predicates.mjs +1 -1
- package/scripts/lib/worktree-notice.mjs +14 -7
- package/scripts/lib/worktree.mjs +3 -2
- package/scripts/utils/resolve-js-root.mjs +2 -1
- package/scripts/utils/with-lock.mjs +1 -1
- package/skills/docgen/js/docgen-batch.mjs +7 -7
- package/skills/docgen/js/docgen-extract.mjs +80 -37
- package/skills/docgen/js/docgen-ignore.mjs +1 -1
- package/skills/docgen/js/docgen-prompts.mjs +21 -5
- package/skills/fix/js/llm-worker.mjs +19 -22
- package/skills/fix/js/orchestrator.mjs +6 -7
- package/skills/fix/js/t0.mjs +14 -13
- package/types/bin/n-cursor.d.ts +1 -1
- package/rules/flow/docs/fix.md +0 -152
- package/rules/flow/fix.mjs +0 -18
- package/rules/flow/flow.mdc +0 -127
- package/rules/flow/meta.json +0 -1
- package/scripts/dispatcher/lib/docs/flow-lock.md +0 -161
- package/scripts/dispatcher/lib/docs/flow-resolve.md +0 -267
- package/scripts/dispatcher/lib/flow-plan.mjs +0 -153
- package/scripts/dispatcher/lib/flow-resolve.mjs +0 -156
- package/scripts/dispatcher/lib/flow-signals.mjs +0 -235
- package/scripts/dispatcher/lib/flow-verify.mjs +0 -127
|
@@ -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 =>
|
|
29
|
+
.filter(l => NON_WHITESPACE_RE.test(l.replace(STAR_INDENT_RE, '')))
|
|
23
30
|
.length
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
/**
|
|
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
|
package/rules/php/docs/fix.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
}
|
package/rules/python/docs/fix.md
CHANGED
|
@@ -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
|
|
67
|
+
// eslint-disable-next-line n/no-process-exit
|
|
65
68
|
process.exit(await runRuleCli(import.meta.dirname))
|
|
66
69
|
}
|
|
67
70
|
```
|
package/rules/python/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
|
|
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
|
}
|
package/rules/rego/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
|
|
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
|
}
|
package/rules/rego/js/lint.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
```
|
package/rules/release/fix.mjs
CHANGED
|
@@ -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
|
|
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
|
}
|
package/rules/rust/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
|
|
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
|
-
|
|
77
|
+
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
Це свідома відмова від загальної заборони `process.exit`: для CLI entry-point потрібно повертати числовий код, інакше CI/IDE не зможуть розрізнити OK і порушення. Заборона залишається активною для решти кодової бази.
|
package/rules/security/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
|
|
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
|
}
|
package/rules/style-lint/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
|
|
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
|
}
|
package/rules/tauri/docs/fix.md
CHANGED
|
@@ -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
|
|
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
|
```
|
package/rules/tauri/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
|
|
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
|
}
|
package/rules/test/docs/fix.md
CHANGED
package/rules/test/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
|
|
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 (
|
|
99
|
+
if (WINDOWS_DRIVE_RE.test(s)) return false
|
|
99
100
|
return true
|
|
100
101
|
}
|
|
101
102
|
|
package/rules/text/docs/fix.md
CHANGED
package/rules/text/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
|
|
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
|
}
|
package/rules/text/js/lint.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
}
|
package/rules/worktree/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
|
|
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
|
}
|
package/scripts/auto-rules.mjs
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
64
|
-
console.warn(`⚠ coverage classify: ${loc} both tiers failed: ${
|
|
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
|
|
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'}`
|
package/scripts/coverage-fix.mjs
CHANGED
|
@@ -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
|
+
}
|