@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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Визначення стану вузла за наявністю файлів (O(1), без читання вмісту).
|
|
3
|
+
* Пріоритет: invalidated > resolved > pending-audit > stalled > running > waiting/blocked > failed > needs-plan
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readdirSync } from 'node:fs'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {'needs-plan'|'waiting'|'blocked'|'running'|'stalled'|'pending-audit'|'resolved'|'failed'|'invalidated'} NodeState
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Визначає стан атомарного вузла за файлами у директорії.
|
|
14
|
+
* @param {string} dir абсолютний шлях до директорії вузла
|
|
15
|
+
* @param {{ depsResolved: boolean }} opts
|
|
16
|
+
* @returns {NodeState}
|
|
17
|
+
*/
|
|
18
|
+
export function deriveAtomicState(dir, { depsResolved = true } = {}) {
|
|
19
|
+
if (existsSync(join(dir, 'invalidated'))) return 'invalidated'
|
|
20
|
+
|
|
21
|
+
const files = listFiles(dir)
|
|
22
|
+
|
|
23
|
+
if (hasFact(files) && !files.includes('invalidated')) return 'resolved'
|
|
24
|
+
|
|
25
|
+
const pendingNNN = findPendingAudit(files)
|
|
26
|
+
if (pendingNNN !== null) return 'pending-audit'
|
|
27
|
+
|
|
28
|
+
const runningUntil = findRunningUntil(files)
|
|
29
|
+
if (runningUntil !== null) {
|
|
30
|
+
const ts = Number(runningUntil)
|
|
31
|
+
const now = Math.floor(Date.now() / 1000)
|
|
32
|
+
return ts > now ? 'running' : 'stalled'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const hasPlan = files.some(f => /^plan_\d{3}\.md$/u.test(f))
|
|
36
|
+
const hasRun = files.some(f => /^run_\d{3}\.md$/u.test(f))
|
|
37
|
+
|
|
38
|
+
if (!hasPlan) return 'needs-plan'
|
|
39
|
+
if (hasRun && !hasFact(files)) return 'failed'
|
|
40
|
+
if (hasPlan && !depsResolved) return 'blocked'
|
|
41
|
+
return 'waiting'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Визначає стан composite вузла за станами дітей.
|
|
46
|
+
* @param {string} dir
|
|
47
|
+
* @param {NodeState[]} childStates
|
|
48
|
+
* @returns {NodeState}
|
|
49
|
+
*/
|
|
50
|
+
export function deriveCompositeState(dir, childStates) {
|
|
51
|
+
if (existsSync(join(dir, 'invalidated'))) return 'invalidated'
|
|
52
|
+
|
|
53
|
+
if (childStates.length === 0) return 'needs-plan'
|
|
54
|
+
if (childStates.every(s => s === 'resolved')) return 'resolved'
|
|
55
|
+
if (childStates.some(s => s === 'running' || s === 'pending-audit')) return 'running'
|
|
56
|
+
if (childStates.some(s => s === 'stalled')) return 'stalled'
|
|
57
|
+
if (childStates.some(s => s === 'failed') && !childStates.some(s => s === 'running')) return 'failed'
|
|
58
|
+
return 'waiting'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Перевіряє наявність orphan worktree для вузла (resolved + worktree exists).
|
|
63
|
+
* @param {string} dir
|
|
64
|
+
* @param {string} worktreesDir
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
export function hasOrphanWorktree(dir, worktreesDir) {
|
|
68
|
+
const nodeName = dir.split('/').at(-1)
|
|
69
|
+
try {
|
|
70
|
+
return readdirSync(worktreesDir).some(d => d.startsWith(nodeName))
|
|
71
|
+
} catch {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- helpers ---
|
|
77
|
+
|
|
78
|
+
/** @param {string} dir @returns {string[]} */
|
|
79
|
+
function listFiles(dir) {
|
|
80
|
+
try { return readdirSync(dir) } catch { return [] }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @param {string[]} files @returns {boolean} */
|
|
84
|
+
function hasFact(files) {
|
|
85
|
+
return files.some(f => /^fact_\d{3}\.md$/u.test(f))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Знаходить перший pending-audit_NNN без audit-result_NNN.
|
|
90
|
+
* @param {string[]} files
|
|
91
|
+
* @returns {string | null} NNN або null
|
|
92
|
+
*/
|
|
93
|
+
function findPendingAudit(files) {
|
|
94
|
+
const pending = files.filter(f => /^pending-audit_\d{3}\.md$/u.test(f)).sort()
|
|
95
|
+
for (const p of pending) {
|
|
96
|
+
const nnn = p.replace('pending-audit_', '').replace('.md', '')
|
|
97
|
+
if (!files.includes(`audit-result_${nnn}.md`)) return nnn
|
|
98
|
+
}
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Знаходить `running_until_<ts>` файл і повертає ts як рядок.
|
|
104
|
+
* @param {string[]} files
|
|
105
|
+
* @returns {string | null}
|
|
106
|
+
*/
|
|
107
|
+
function findRunningUntil(files) {
|
|
108
|
+
const f = files.find(f => /^running_until_\d+$/u.test(f))
|
|
109
|
+
return f ? f.replace('running_until_', '') : null
|
|
110
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `graph scan` — повне сканування граф: відновлює стан з файлів, виводить результат.
|
|
3
|
+
* exit 0 — граф чистий; exit 1 — є вузли у стані failed.
|
|
4
|
+
*/
|
|
5
|
+
import { resolve } from 'node:path'
|
|
6
|
+
import { cwd } from 'node:process'
|
|
7
|
+
|
|
8
|
+
import { loadConfig } from './lib/config.mjs'
|
|
9
|
+
import { buildDag } from './lib/dag.mjs'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string[]} args
|
|
13
|
+
* @param {{ json?: boolean }} opts
|
|
14
|
+
*/
|
|
15
|
+
export async function runScan(args, opts = {}) {
|
|
16
|
+
const root = cwd()
|
|
17
|
+
const config = loadConfig(root)
|
|
18
|
+
const tasksDir = resolve(root, config.tasks_dir)
|
|
19
|
+
const worktreesDir = resolve(root, config.worktrees_dir)
|
|
20
|
+
|
|
21
|
+
const nodes = buildDag(tasksDir, worktreesDir)
|
|
22
|
+
|
|
23
|
+
if (opts.json) {
|
|
24
|
+
const out = {}
|
|
25
|
+
for (const [id, node] of nodes) out[id] = { state: node.state, deps: node.deps }
|
|
26
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n')
|
|
27
|
+
} else {
|
|
28
|
+
printScanTable(nodes)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const hasFailed = [...nodes.values()].some(n => n.state === 'failed')
|
|
32
|
+
return hasFailed ? 1 : 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @param {Map<string, import('./lib/dag.mjs').GraphNode>} nodes */
|
|
36
|
+
function printScanTable(nodes) {
|
|
37
|
+
const STATE_ICON = {
|
|
38
|
+
'needs-plan': '⏳',
|
|
39
|
+
waiting: '○',
|
|
40
|
+
blocked: '○',
|
|
41
|
+
running: '◉',
|
|
42
|
+
stalled: '⚠',
|
|
43
|
+
'pending-audit': '🔍',
|
|
44
|
+
resolved: '✓',
|
|
45
|
+
failed: '✗',
|
|
46
|
+
invalidated: '⊘',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const counts = {}
|
|
50
|
+
for (const { state } of nodes.values()) counts[state] = (counts[state] ?? 0) + 1
|
|
51
|
+
|
|
52
|
+
const summary = Object.entries(counts).map(([s, n]) => `${s}:${n}`).join(' ')
|
|
53
|
+
console.log(`graph — ${summary}\n`)
|
|
54
|
+
|
|
55
|
+
for (const [id, node] of nodes) {
|
|
56
|
+
const icon = STATE_ICON[node.state] ?? '?'
|
|
57
|
+
let detail = ''
|
|
58
|
+
if (node.state === 'needs-plan') detail = `run: graph plan tasks/${id}/`
|
|
59
|
+
else if (node.state === 'blocked') detail = `blocked: ${node.deps.join(', ')}`
|
|
60
|
+
else if (node.state === 'stalled') detail = 'stalled — deadline passed'
|
|
61
|
+
const suffix = detail ? ` [${detail}]` : ''
|
|
62
|
+
console.log(` ${icon} ${id.padEnd(30)} [${node.state}]${suffix}`)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `graph status [path]` — стан одного вузла або всього графу.
|
|
3
|
+
*/
|
|
4
|
+
import { resolve } from 'node:path'
|
|
5
|
+
import { cwd } from 'node:process'
|
|
6
|
+
|
|
7
|
+
import { loadConfig } from './lib/config.mjs'
|
|
8
|
+
import { buildDag } from './lib/dag.mjs'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string | undefined} path відносний шлях до вузла або undefined для всього графу
|
|
12
|
+
* @param {{ json?: boolean }} opts
|
|
13
|
+
*/
|
|
14
|
+
export async function runStatus(path, opts = {}) {
|
|
15
|
+
const root = cwd()
|
|
16
|
+
const config = loadConfig(root)
|
|
17
|
+
const tasksDir = resolve(root, config.tasks_dir)
|
|
18
|
+
const worktreesDir = resolve(root, config.worktrees_dir)
|
|
19
|
+
|
|
20
|
+
const nodes = buildDag(tasksDir, worktreesDir)
|
|
21
|
+
|
|
22
|
+
if (path) {
|
|
23
|
+
// Normalize: tasks/foo → foo, tasks/foo/ → foo
|
|
24
|
+
const id = path.replace(/^tasks\//u, '').replace(/\/$/u, '')
|
|
25
|
+
const node = nodes.get(id)
|
|
26
|
+
if (!node) {
|
|
27
|
+
console.error(`Node not found: ${id}`)
|
|
28
|
+
return 1
|
|
29
|
+
}
|
|
30
|
+
if (opts.json) {
|
|
31
|
+
process.stdout.write(JSON.stringify({ id, ...node }, null, 2) + '\n')
|
|
32
|
+
} else {
|
|
33
|
+
printNode(id, node, nodes)
|
|
34
|
+
}
|
|
35
|
+
return 0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Full graph
|
|
39
|
+
if (opts.json) {
|
|
40
|
+
const out = {}
|
|
41
|
+
for (const [id, node] of nodes) {
|
|
42
|
+
out[id] = { state: node.state, deps: node.deps, mode: node.mode, executor: node.executor }
|
|
43
|
+
}
|
|
44
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n')
|
|
45
|
+
} else {
|
|
46
|
+
// Print as tree
|
|
47
|
+
const roots = [...nodes.values()].filter(n => !n.parentId)
|
|
48
|
+
const counts = {}
|
|
49
|
+
for (const { state } of nodes.values()) counts[state] = (counts[state] ?? 0) + 1
|
|
50
|
+
const summary = Object.entries(counts).map(([s, n]) => `${s}:${n}`).join(' ')
|
|
51
|
+
console.log(`graph — ${summary}\n`)
|
|
52
|
+
for (const root of roots) printTree(root.id, nodes, 0)
|
|
53
|
+
}
|
|
54
|
+
return 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const ICON = {
|
|
58
|
+
'needs-plan': '⏳', waiting: '○', blocked: '○', running: '◉',
|
|
59
|
+
stalled: '⚠', 'pending-audit': '🔍', resolved: '✓', failed: '✗', invalidated: '⊘',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** @param {string} id @param {Map<string, import('./lib/dag.mjs').GraphNode>} nodes @param {number} depth */
|
|
63
|
+
function printTree(id, nodes, depth) {
|
|
64
|
+
const node = nodes.get(id)
|
|
65
|
+
if (!node) return
|
|
66
|
+
const icon = ICON[node.state] ?? '?'
|
|
67
|
+
const indent = ' '.repeat(depth)
|
|
68
|
+
let detail = ''
|
|
69
|
+
if (node.state === 'needs-plan') detail = ` run: graph plan tasks/${id}/`
|
|
70
|
+
else if (node.state === 'blocked') detail = ` blocked: ${node.deps.join(', ')}`
|
|
71
|
+
console.log(`${indent}${icon} ${id} [${node.state}]${detail}`)
|
|
72
|
+
for (const child of node.children) printTree(child, nodes, depth + 1)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} id
|
|
77
|
+
* @param {import('./lib/dag.mjs').GraphNode} node
|
|
78
|
+
* @param {Map<string, import('./lib/dag.mjs').GraphNode>} nodes
|
|
79
|
+
*/
|
|
80
|
+
function printNode(id, node, nodes) {
|
|
81
|
+
const icon = ICON[node.state] ?? '?'
|
|
82
|
+
console.log(`${icon} ${id} [${node.state}]`)
|
|
83
|
+
if (node.deps.length) console.log(` deps: ${node.deps.join(', ')}`)
|
|
84
|
+
if (node.children.length) console.log(` children: ${node.children.join(', ')}`)
|
|
85
|
+
console.log(` mode: ${node.mode} executor: ${node.executor.type}/${node.executor.model_tier}`)
|
|
86
|
+
}
|
|
@@ -101,6 +101,9 @@ const ignored = await loadCursorIgnorePaths(root)
|
|
|
101
101
|
// ignored: ['/abs/path/.worktrees', '/abs/path/node_modules', ...]
|
|
102
102
|
|
|
103
103
|
// Далі при обході файлів пропускаємо ті, що під будь-яким із ignored:
|
|
104
|
+
/**
|
|
105
|
+
*
|
|
106
|
+
*/
|
|
104
107
|
function isIgnored(absPosixPath) {
|
|
105
108
|
return ignored.some(ig => absPosixPath === ig || absPosixPath.startsWith(ig + '/'))
|
|
106
109
|
}
|
|
@@ -40,6 +40,8 @@ git rev-parse --show-toplevel
|
|
|
40
40
|
/** Канонічний блок root-інструкції (з маркерами). */
|
|
41
41
|
const BLOCK = `${ROOT_START}\n${NOTICE_BODY}\n${ROOT_END}`
|
|
42
42
|
|
|
43
|
+
const LEADING_NEWLINES_RE = /^\n+/u
|
|
44
|
+
|
|
43
45
|
/**
|
|
44
46
|
* Вставляє / оновлює / видаляє root-guard блок у вмісті `SKILL.md`.
|
|
45
47
|
* @param {string} content вміст `SKILL.md`
|
|
@@ -57,8 +59,8 @@ export function injectRootNotice(content, enabled) {
|
|
|
57
59
|
const fm = withoutBlock.match(FRONTMATTER_RE)
|
|
58
60
|
if (fm) {
|
|
59
61
|
const head = fm[1]
|
|
60
|
-
const rest = withoutBlock.slice(head.length).replace(
|
|
62
|
+
const rest = withoutBlock.slice(head.length).replace(LEADING_NEWLINES_RE, '')
|
|
61
63
|
return `${head}\n${BLOCK}\n\n${rest}`
|
|
62
64
|
}
|
|
63
|
-
return `${BLOCK}\n\n${withoutBlock.replace(
|
|
65
|
+
return `${BLOCK}\n\n${withoutBlock.replace(LEADING_NEWLINES_RE, '')}`
|
|
64
66
|
}
|
|
@@ -133,7 +133,7 @@ export const RULE_PREDICATES = {
|
|
|
133
133
|
* @param {{ hasBunSqlImport: boolean }} facts факти
|
|
134
134
|
* @returns {Promise<boolean>} true, якщо deps pg/pg-format/mysql2 або import sql з bun
|
|
135
135
|
*/
|
|
136
|
-
|
|
136
|
+
jsBunDbSignal(cwd, facts) {
|
|
137
137
|
if (facts.hasBunSqlImport === true) return true
|
|
138
138
|
return anyDepInTree(cwd, ['pg', 'pg-format', 'mysql2'])
|
|
139
139
|
},
|
|
@@ -36,6 +36,13 @@ const NAME_RE = /^name:\s*["']?([^"'\n]+)["']?\s*$/mu
|
|
|
36
36
|
/** Перший H1 як fallback, якщо frontmatter не містить `name`. */
|
|
37
37
|
const H1_RE = /^#\s+(.+)$/mu
|
|
38
38
|
|
|
39
|
+
const N_PREFIX_RE = /^n-/u
|
|
40
|
+
const COMBINING_DIACRITICS_RE = /[̀-ͯ]/gu
|
|
41
|
+
const NON_ALPHANUM_RE = /[^a-z0-9]+/gu
|
|
42
|
+
const TRAILING_DASHES_RE = /^-+|-+$/gu
|
|
43
|
+
const TRAILING_DASH_RE = /-+$/u
|
|
44
|
+
const LEADING_NEWLINES_RE = /^\n+/u
|
|
45
|
+
|
|
39
46
|
const CYRILLIC_TRANSLIT = new Map(
|
|
40
47
|
Object.entries({
|
|
41
48
|
а: 'a',
|
|
@@ -96,13 +103,13 @@ function deriveSuffix(content) {
|
|
|
96
103
|
const raw = content.match(NAME_RE)?.[1] ?? content.match(H1_RE)?.[1] ?? FALLBACK_SUFFIX
|
|
97
104
|
const slug = transliterate(raw)
|
|
98
105
|
.trim()
|
|
99
|
-
.replace(
|
|
106
|
+
.replace(N_PREFIX_RE, '')
|
|
100
107
|
.normalize('NFKD')
|
|
101
|
-
.replaceAll(
|
|
102
|
-
.replaceAll(
|
|
103
|
-
.replaceAll(
|
|
108
|
+
.replaceAll(COMBINING_DIACRITICS_RE, '')
|
|
109
|
+
.replaceAll(NON_ALPHANUM_RE, '-')
|
|
110
|
+
.replaceAll(TRAILING_DASHES_RE, '')
|
|
104
111
|
|
|
105
|
-
return (slug || FALLBACK_SUFFIX).slice(0, 10).replace(
|
|
112
|
+
return (slug || FALLBACK_SUFFIX).slice(0, 10).replace(TRAILING_DASH_RE, '') || FALLBACK_SUFFIX
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
/**
|
|
@@ -202,8 +209,8 @@ export function injectWorktreeNotice(content, enabled) {
|
|
|
202
209
|
const fm = withoutBlock.match(FRONTMATTER_RE)
|
|
203
210
|
if (fm) {
|
|
204
211
|
const head = fm[1]
|
|
205
|
-
const rest = withoutBlock.slice(head.length).replace(
|
|
212
|
+
const rest = withoutBlock.slice(head.length).replace(LEADING_NEWLINES_RE, '')
|
|
206
213
|
return `${head}\n${block}\n\n${rest}`
|
|
207
214
|
}
|
|
208
|
-
return `${block}\n\n${withoutBlock.replace(
|
|
215
|
+
return `${block}\n\n${withoutBlock.replace(LEADING_NEWLINES_RE, '')}`
|
|
209
216
|
}
|
package/scripts/lib/worktree.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { basename, join } from 'node:path'
|
|
|
14
14
|
|
|
15
15
|
/** Символи, безпечні для імені каталогу/файла; решта → дефіс. */
|
|
16
16
|
const UNSAFE_PATH_CHARS_RE = /[^a-zA-Z0-9._-]+/gu
|
|
17
|
+
const MD_EXTENSION_RE = /\.md$/u
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Перетворює імʼя git-гілки на безпечне імʼя каталогу/файла для `.worktrees/`.
|
|
@@ -82,7 +83,7 @@ export function buildDescription({ branch, task, baseCommit, date }) {
|
|
|
82
83
|
`**Дата:** ${date}`,
|
|
83
84
|
`**База (коміт):** ${baseCommit}`,
|
|
84
85
|
'',
|
|
85
|
-
|
|
86
|
+
`Прибрати: \`npx @nitra/cursor worktree remove ${branch}\``,
|
|
86
87
|
''
|
|
87
88
|
].join('\n')
|
|
88
89
|
}
|
|
@@ -121,5 +122,5 @@ export function buildDirtyNotice(porcelain, limit = DIRTY_LIST_LIMIT) {
|
|
|
121
122
|
*/
|
|
122
123
|
export function findOrphanDescFiles(descFiles, registeredCheckouts) {
|
|
123
124
|
const checkoutBasenames = new Set(registeredCheckouts.map(c => basename(c)))
|
|
124
|
-
return descFiles.filter(md => !checkoutBasenames.has(basename(md).replace(
|
|
125
|
+
return descFiles.filter(md => !checkoutBasenames.has(basename(md).replace(MD_EXTENSION_RE, '')))
|
|
125
126
|
}
|
|
@@ -8,6 +8,7 @@ import { glob, readFile } from 'node:fs/promises'
|
|
|
8
8
|
import { join } from 'node:path'
|
|
9
9
|
|
|
10
10
|
const WORKSPACE_GLOB_IGNORE = ['**/node_modules/**', '**/.git/**']
|
|
11
|
+
const PACKAGE_JSON_SUFFIX_RE = /[/\\]package\.json$/
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Розгортає один workspace-патерн у список абсолютних шляхів каталогів з package.json.
|
|
@@ -23,7 +24,7 @@ async function expandWorkspacePattern(cwd, pattern) {
|
|
|
23
24
|
}
|
|
24
25
|
const results = []
|
|
25
26
|
for await (const rel of glob(`${pattern}/package.json`, { cwd, exclude: WORKSPACE_GLOB_IGNORE })) {
|
|
26
|
-
const wsRel = rel.replace(
|
|
27
|
+
const wsRel = rel.replace(PACKAGE_JSON_SUFFIX_RE, '')
|
|
27
28
|
results.push(join(cwd, wsRel))
|
|
28
29
|
}
|
|
29
30
|
return results.sort()
|
|
@@ -128,7 +128,7 @@ export async function withLock(key, runFn, opts = {}) {
|
|
|
128
128
|
|
|
129
129
|
const onSignal = () => {
|
|
130
130
|
release()
|
|
131
|
-
// eslint-disable-next-line n/no-process-exit
|
|
131
|
+
// eslint-disable-next-line n/no-process-exit -- SIGINT/SIGTERM мають завершити процес із кодом 130
|
|
132
132
|
process.exit(130)
|
|
133
133
|
}
|
|
134
134
|
process.once('SIGINT', onSignal)
|
|
@@ -20,8 +20,8 @@ const missing = allFiles.filter(x => !x.exists)
|
|
|
20
20
|
console.log(`\n📋 Файлів для генерації: ${missing.length}`)
|
|
21
21
|
|
|
22
22
|
// 2. Розкласти по тирах
|
|
23
|
-
const
|
|
24
|
-
|
|
23
|
+
const cloud = [],
|
|
24
|
+
local = []
|
|
25
25
|
for (const f of missing) {
|
|
26
26
|
try {
|
|
27
27
|
const src = readFileSync(join(ROOT, f.sourcePath), 'utf8')
|
|
@@ -51,10 +51,10 @@ for (const f of cloud) {
|
|
|
51
51
|
stats.ok++
|
|
52
52
|
stats.cloudOk++
|
|
53
53
|
console.log(` ✓ ${f.sourcePath} (sym=${f.sym}, ${Math.round((Date.now() - t0) / 1000)}s)`)
|
|
54
|
-
} catch (
|
|
54
|
+
} catch (error) {
|
|
55
55
|
stats.err++
|
|
56
56
|
stats.errors.push(f.sourcePath)
|
|
57
|
-
console.error(` ✗ ${f.sourcePath}: ${
|
|
57
|
+
console.error(` ✗ ${f.sourcePath}: ${error.message}`)
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -77,10 +77,10 @@ for (const f of local) {
|
|
|
77
77
|
stats.ok++
|
|
78
78
|
stats.localOk++
|
|
79
79
|
process.stdout.write(`✓ ${Math.round((Date.now() - t0) / 1000)}s score=${result.score ?? '?'}\n`)
|
|
80
|
-
} catch (
|
|
80
|
+
} catch (error) {
|
|
81
81
|
stats.err++
|
|
82
82
|
stats.errors.push(f.sourcePath)
|
|
83
|
-
process.stdout.write(`✗ ${
|
|
83
|
+
process.stdout.write(`✗ ${error.message}\n`)
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -91,5 +91,5 @@ console.log(` 💻 Local (gemma3:4b): ${stats.localOk} файлів`)
|
|
|
91
91
|
console.log(` ☁️ Cloud (Claude/pi): ${stats.cloudOk} файлів`)
|
|
92
92
|
if (stats.errors.length > 0) {
|
|
93
93
|
console.log('Помилки:')
|
|
94
|
-
stats.errors
|
|
94
|
+
for (const e of stats.errors) console.log(` - ${e}`)
|
|
95
95
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** @see ./docs/docgen-extract.md */
|
|
2
2
|
|
|
3
|
+
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
4
|
+
import { readFileSync } from 'node:fs'
|
|
5
|
+
|
|
3
6
|
const BUILTIN_MODULES = new Set([
|
|
4
7
|
'fs',
|
|
5
8
|
'path',
|
|
@@ -19,18 +22,45 @@ const BUILTIN_MODULES = new Set([
|
|
|
19
22
|
'readline'
|
|
20
23
|
])
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
const JSDOC_OPEN_RE = /^\s*\/\*\*?/
|
|
26
|
+
const JSDOC_CLOSE_RE = /\*\/\s*$/
|
|
27
|
+
const STAR_PREFIX_RE = /^\s*\*?\s?/
|
|
28
|
+
const PARAM_LINE_RE = /^@param\s+(?:\{[^}]*\}\s+)?\[?([A-Za-z0-9_.]+)\]?\s*(.*)$/
|
|
29
|
+
const RETURNS_LINE_RE = /^@returns?\s+(?:\{[^}]*\}\s+)?(.*)$/
|
|
30
|
+
const FILE_HEADER_RE = /^\s*\/\*\*([\s\S]*?)\*\//
|
|
31
|
+
const PRECEDING_JSDOC_RE = /\/\*\*(?:(?!\*\/)[\s\S])*\*\/\s*$/
|
|
32
|
+
const EXPORT_DECL_RE = /export\s+(?:async\s+)?(function|const|class)\s+([A-Za-z0-9_]+)/g
|
|
33
|
+
const IMPORT_FROM_RE = /^import\s+[\s\S]*?from\s+['"]([^'"]+)['"]/gm
|
|
34
|
+
const NODE_PREFIX_RE = /^node:/
|
|
35
|
+
const INTERNAL_IMPORT_RE = /import\s+(?:([A-Za-z0-9_$]+)\s*,?\s*)?(?:\{([^}]+)\})?\s+from\s+['"](\.[^'"]+)['"]/g
|
|
36
|
+
const IMPORT_AS_RE = /\s+as\s+.*/
|
|
37
|
+
const WRITE_FS_RE = /\b(writeFile|mkdir|rmdir|unlink|appendFile|createWriteStream|rm\()/
|
|
38
|
+
const CATCH_RE = /catch\s*\(/
|
|
39
|
+
const TRY_RE = /\btry\s*\{/
|
|
40
|
+
const FALSY_RETURN_RE = /return\s+(false|null|''|"")/
|
|
41
|
+
const NETWORK_RE = /\bfetch\(|https?\.|axios|got\(/
|
|
42
|
+
const CACHE_RE = /new Map\(\)|Cache|cache/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Прибирає `/** */`-обрамлення й `*`-префікси, повертає чистий текст рядками.
|
|
46
|
+
* @param {string} raw сирий JSDoc-блок з обрамленням
|
|
47
|
+
* @returns {string} очищений текст без обрамлення й префіксів
|
|
48
|
+
*/
|
|
23
49
|
function cleanJsDoc(raw) {
|
|
24
50
|
return raw
|
|
25
|
-
.replace(
|
|
26
|
-
.replace(
|
|
51
|
+
.replace(JSDOC_OPEN_RE, '')
|
|
52
|
+
.replace(JSDOC_CLOSE_RE, '')
|
|
27
53
|
.split('\n')
|
|
28
|
-
.map(l => l.replace(
|
|
54
|
+
.map(l => l.replace(STAR_PREFIX_RE, '').trimEnd())
|
|
29
55
|
.join('\n')
|
|
30
56
|
.trim()
|
|
31
57
|
}
|
|
32
58
|
|
|
33
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Опис (без @-тегів) + параметри з @param як «name — опис».
|
|
61
|
+
* @param {string} raw сирий JSDoc-блок
|
|
62
|
+
* @returns {{desc:string, params:Array<{name:string, desc:string}>, ret:string}} розпарсений опис, параметри й опис повернення
|
|
63
|
+
*/
|
|
34
64
|
function parseJsDoc(raw) {
|
|
35
65
|
const text = cleanJsDoc(raw)
|
|
36
66
|
const lines = text.split('\n')
|
|
@@ -38,8 +68,8 @@ function parseJsDoc(raw) {
|
|
|
38
68
|
const params = []
|
|
39
69
|
let ret = ''
|
|
40
70
|
for (const l of lines) {
|
|
41
|
-
const pm = l.match(
|
|
42
|
-
const rm = l.match(
|
|
71
|
+
const pm = l.match(PARAM_LINE_RE)
|
|
72
|
+
const rm = l.match(RETURNS_LINE_RE)
|
|
43
73
|
if (pm) {
|
|
44
74
|
const desc = pm[2].trim()
|
|
45
75
|
// «опис.» — JSDoc-заглушка без сенсу; не тягнемо її як факт
|
|
@@ -56,9 +86,13 @@ function parseJsDoc(raw) {
|
|
|
56
86
|
return { desc: descLines.join('\n').trim(), params, ret }
|
|
57
87
|
}
|
|
58
88
|
|
|
59
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Провідний блок-коментар файлу (намір), якщо він перед першим import/кодом.
|
|
91
|
+
* @param {string} src вміст файлу
|
|
92
|
+
* @returns {string} текст header-коментаря або порожній рядок
|
|
93
|
+
*/
|
|
60
94
|
function extractFileHeader(src) {
|
|
61
|
-
const m = src.match(
|
|
95
|
+
const m = src.match(FILE_HEADER_RE)
|
|
62
96
|
if (!m) return ''
|
|
63
97
|
// має бути на самому початку (до import/код)
|
|
64
98
|
if (src.slice(0, m.index).trim() !== '') return ''
|
|
@@ -69,18 +103,22 @@ function extractFileHeader(src) {
|
|
|
69
103
|
* Блок-коментар, що стоїть ВПРИТУЛ перед позицією (лише пробіли між ними).
|
|
70
104
|
* `(?:(?!\*/)[\s\S])*` гарантує, що тіло не містить `*/`, тож захоплюється рівно один
|
|
71
105
|
* найближчий блок — без жадібного «перестрибування» через імпорти/код.
|
|
106
|
+
* @param {string} prefix вміст файлу до позиції експорту
|
|
107
|
+
* @returns {string|null} JSDoc-блок або null якщо немає
|
|
72
108
|
*/
|
|
73
109
|
function precedingJsDoc(prefix) {
|
|
74
|
-
const m = prefix.match(
|
|
110
|
+
const m = prefix.match(PRECEDING_JSDOC_RE)
|
|
75
111
|
return m ? m[0] : null
|
|
76
112
|
}
|
|
77
113
|
|
|
78
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Експорти + JSDoc, що безпосередньо передує кожному.
|
|
116
|
+
* @param {string} src вміст файлу
|
|
117
|
+
* @returns {Array<object>} список експортів із метаданими
|
|
118
|
+
*/
|
|
79
119
|
function extractExports(src) {
|
|
80
120
|
const out = []
|
|
81
|
-
const
|
|
82
|
-
let m
|
|
83
|
-
while ((m = re.exec(src))) {
|
|
121
|
+
for (const m of src.matchAll(EXPORT_DECL_RE)) {
|
|
84
122
|
const [, kind, name] = m
|
|
85
123
|
const jsdocRaw = precedingJsDoc(src.slice(0, m.index))
|
|
86
124
|
out.push({ name, kind, ...(jsdocRaw ? parseJsDoc(jsdocRaw) : { desc: '', params: [], ret: '' }) })
|
|
@@ -88,39 +126,47 @@ function extractExports(src) {
|
|
|
88
126
|
return out
|
|
89
127
|
}
|
|
90
128
|
|
|
91
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* Імпорти, класифіковані на stdlib / npm / internal.
|
|
131
|
+
* @param {string} src вміст файлу
|
|
132
|
+
* @returns {{stdlib:Array<string>, npm:Array<string>, internal:Array<string>}} розкласифіковані шляхи імпортів
|
|
133
|
+
*/
|
|
92
134
|
function extractImports(src) {
|
|
93
|
-
const
|
|
135
|
+
const internal = new Set(),
|
|
94
136
|
npm = new Set(),
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
let m
|
|
98
|
-
while ((m = re.exec(src))) {
|
|
137
|
+
stdlib = new Set()
|
|
138
|
+
for (const m of src.matchAll(IMPORT_FROM_RE)) {
|
|
99
139
|
const s = m[1]
|
|
100
|
-
if (s.startsWith('node:') || BUILTIN_MODULES.has(s.split('/')[0])) stdlib.add(s.replace(
|
|
140
|
+
if (s.startsWith('node:') || BUILTIN_MODULES.has(s.split('/')[0])) stdlib.add(s.replace(NODE_PREFIX_RE, ''))
|
|
101
141
|
else if (s.startsWith('.') || s.startsWith('/')) internal.add(s)
|
|
102
142
|
else npm.add(s)
|
|
103
143
|
}
|
|
104
144
|
return { stdlib: [...stdlib], npm: [...npm], internal: [...internal] }
|
|
105
145
|
}
|
|
106
146
|
|
|
107
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Імена символів, імпортованих із внутрішніх модулів — їх модель не має згадувати.
|
|
149
|
+
* @param {string} src вміст файлу
|
|
150
|
+
* @returns {Array<string>} список імен внутрішніх символів
|
|
151
|
+
*/
|
|
108
152
|
function extractInternalSymbols(src) {
|
|
109
153
|
const out = new Set()
|
|
110
|
-
const
|
|
111
|
-
let m
|
|
112
|
-
while ((m = re.exec(src))) {
|
|
154
|
+
for (const m of src.matchAll(INTERNAL_IMPORT_RE)) {
|
|
113
155
|
if (m[1]) out.add(m[1].trim())
|
|
114
156
|
if (m[2])
|
|
115
157
|
for (const n of m[2].split(',')) {
|
|
116
|
-
const name = n.replace(
|
|
158
|
+
const name = n.replace(IMPORT_AS_RE, '').trim()
|
|
117
159
|
if (name) out.add(name)
|
|
118
160
|
}
|
|
119
161
|
}
|
|
120
162
|
return [...out]
|
|
121
163
|
}
|
|
122
164
|
|
|
123
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* Поведінкові маркери — евристики регулярками.
|
|
167
|
+
* @param {string} src вміст файлу
|
|
168
|
+
* @returns {object} набір прапорців-евристик
|
|
169
|
+
*/
|
|
124
170
|
function extractMarkers(src) {
|
|
125
171
|
// помітні «пропуски»: dir/segment-літерали у фільтрах
|
|
126
172
|
const skips = new Set()
|
|
@@ -128,11 +174,11 @@ function extractMarkers(src) {
|
|
|
128
174
|
if (src.includes(`'${lit}`) || src.includes(`"${lit}`) || src.includes(`/${lit}`)) skips.add(lit)
|
|
129
175
|
}
|
|
130
176
|
return {
|
|
131
|
-
readOnly:
|
|
132
|
-
catchesErrors:
|
|
133
|
-
returnsFalsyOnFail:
|
|
134
|
-
network:
|
|
135
|
-
caches:
|
|
177
|
+
readOnly: !WRITE_FS_RE.test(src),
|
|
178
|
+
catchesErrors: CATCH_RE.test(src) || TRY_RE.test(src),
|
|
179
|
+
returnsFalsyOnFail: FALSY_RETURN_RE.test(src),
|
|
180
|
+
network: NETWORK_RE.test(src),
|
|
181
|
+
caches: CACHE_RE.test(src),
|
|
136
182
|
skips: [...skips]
|
|
137
183
|
}
|
|
138
184
|
}
|
|
@@ -141,7 +187,7 @@ function extractMarkers(src) {
|
|
|
141
187
|
* Головний екстрактор: код файлу → факт-лист.
|
|
142
188
|
* @param {string} src вміст файлу
|
|
143
189
|
* @param {string} relPath шлях (для контексту/мови екстрактора)
|
|
144
|
-
* @returns {{relPath:string, lang:string, header:string, exports:Array, imports:object, markers:object}}
|
|
190
|
+
* @returns {{relPath:string, lang:string, header:string, exports:Array, imports:object, markers:object}} структура фактів про файл
|
|
145
191
|
*/
|
|
146
192
|
export function extractFacts(src, relPath) {
|
|
147
193
|
const lang = relPath.split('.').pop()
|
|
@@ -160,13 +206,10 @@ export function extractFacts(src, relPath) {
|
|
|
160
206
|
}
|
|
161
207
|
|
|
162
208
|
// CLI для інспекції: node docgen-extract.mjs <file>
|
|
163
|
-
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
164
|
-
import { readFileSync } from 'node:fs'
|
|
165
209
|
if (isRunAsCli(import.meta.url)) {
|
|
166
210
|
const file = process.argv[2]
|
|
167
211
|
if (!file) {
|
|
168
|
-
|
|
169
|
-
process.exit(1)
|
|
212
|
+
throw new Error('Usage: node docgen-extract.mjs <file>')
|
|
170
213
|
}
|
|
171
214
|
const facts = extractFacts(readFileSync(file, 'utf8'), file)
|
|
172
215
|
console.log(JSON.stringify(facts, null, 2))
|
|
@@ -36,7 +36,7 @@ function toPosixRelPath(relPath) {
|
|
|
36
36
|
* Для `kind = 'dir'` це працює і на піддерево каталогу, тож glob на кшталт
|
|
37
37
|
* `**\\/demo/**` спрацьовує на `demo/x` під час рекурсивного обходу.
|
|
38
38
|
* @param {string} relPath відносний шлях від кореня проєкту
|
|
39
|
-
* @param {'path'|'dir'} [kind
|
|
39
|
+
* @param {'path'|'dir'} [kind] тип перевірки (за замовчуванням `'path'`)
|
|
40
40
|
* @returns {boolean} `true`, якщо шлях ігнорується
|
|
41
41
|
*/
|
|
42
42
|
export function isDocgenIgnored(relPath, kind = 'path') {
|