@nitra/cursor 12.9.0 → 12.11.0
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/.claude-template/settings.template.json +1 -23
- package/CHANGELOG.md +12 -0
- package/bin/n-cursor.js +8 -43
- package/docs/stryker.config.md +0 -2
- package/lib/docs/llm.md +16 -21
- package/lib/docs/omlx.md +20 -25
- package/lib/llm.mjs +25 -7
- package/lib/omlx.mjs +10 -3
- package/package.json +1 -1
- package/rules/abie/docs/main.md +0 -2
- package/rules/abie/lib/docs/http-route.md +0 -2
- package/rules/abie/main.mdc +0 -22
- package/rules/adr/docs/main.md +0 -2
- package/rules/adr/js/docs/hooks.md +16 -26
- package/rules/adr/js/hooks.mjs +59 -0
- package/rules/adr/main.mdc +0 -9
- package/rules/bun/docs/main.md +0 -2
- package/rules/bun/main.mdc +1 -15
- package/rules/capacitor/docs/main.md +0 -2
- package/rules/capacitor/main.mdc +0 -6
- package/rules/changelog/docs/main.md +0 -2
- package/rules/changelog/js/agent-workflow.mdc +1 -1
- package/rules/changelog/js/consistency.mjs +57 -3
- package/rules/changelog/js/docs/consistency.md +26 -24
- package/rules/changelog/js/docs/index.md +2 -2
- package/rules/changelog/main.mdc +0 -5
- package/rules/ci4/docs/main.md +0 -2
- package/rules/ci4/main.mdc +0 -5
- package/rules/doc-files/docs/main.md +0 -2
- package/rules/doc-files/js/docs/docgen-crc.md +0 -2
- package/rules/doc-files/js/docs/docgen-extract.md +0 -2
- package/rules/doc-files/js/docs/docgen-files-batch.md +0 -2
- package/rules/doc-files/js/docs/docgen-gen.md +0 -2
- package/rules/doc-files/js/docs/docgen-judge-measure.md +0 -2
- package/rules/doc-files/js/docs/docgen-judge.md +0 -2
- package/rules/doc-files/js/docs/docgen-scan.md +0 -2
- package/rules/doc-files/js/docs/run-lint.md +0 -2
- package/rules/docker/docs/main.md +0 -2
- package/rules/docker/js/docs/lint.md +0 -2
- package/rules/docker/lib/docs/docker-hadolint.md +0 -2
- package/rules/docker/main.mdc +1 -21
- package/rules/efes/docs/main.md +0 -2
- package/rules/efes/main.mdc +0 -1
- package/rules/feedback/docs/main.md +0 -2
- package/rules/ga/docs/main.md +0 -2
- package/rules/ga/js/docs/index.md +0 -1
- package/rules/ga/main.mdc +1 -31
- package/rules/graphql/docs/main.md +0 -2
- package/rules/graphql/main.mdc +0 -5
- package/rules/hasura/docs/main.md +0 -2
- package/rules/hasura/js/docs/index.md +3 -3
- package/rules/hasura/js/docs/migrations.md +0 -2
- package/rules/hasura/main.mdc +1 -11
- package/rules/image-avif/docs/main.md +0 -2
- package/rules/image-avif/main.mdc +1 -9
- package/rules/image-compress/docs/main.md +0 -2
- package/rules/image-compress/js/docs/index.md +0 -1
- package/rules/image-compress/main.mdc +1 -9
- package/rules/js/docs/main.md +0 -2
- package/rules/js/js/dep-policy.mjs +8 -4
- package/rules/js/js/docs/check.md +0 -2
- package/rules/js/js/docs/dep-policy.md +10 -12
- package/rules/js/js/docs/index.md +5 -5
- package/rules/js/js/docs/tooling.md +0 -2
- package/rules/js/js/docs/utils_imports.md +0 -2
- package/rules/js/main.mdc +0 -31
- package/rules/js-bun-db/docs/main.md +0 -2
- package/rules/js-bun-db/js/docs/safety.md +18 -23
- package/rules/js-bun-db/js/safety.mjs +31 -3
- package/rules/js-bun-db/lib/bun-sql-scan.mjs +123 -0
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +37 -331
- package/rules/js-bun-db/main.mdc +1 -23
- package/rules/js-bun-redis/docs/main.md +0 -2
- package/rules/js-bun-redis/main.mdc +0 -5
- package/rules/js-mssql/docs/main.md +0 -2
- package/rules/js-mssql/main.mdc +0 -12
- package/rules/js-run/docs/main.md +0 -2
- package/rules/js-run/js/docs/runtime.md +15 -13
- package/rules/js-run/js/runtime.mjs +48 -4
- package/rules/js-run/main.mdc +0 -25
- package/rules/k8s/docs/main.md +0 -2
- package/rules/k8s/main.mdc +0 -45
- package/rules/nginx-default-tpl/docs/main.md +0 -2
- package/rules/nginx-default-tpl/main.mdc +0 -13
- package/rules/npm-module/docs/main.md +0 -2
- package/rules/npm-module/js/docs/header_doc_pointer.md +0 -2
- package/rules/npm-module/js/docs/rule_meta.md +0 -2
- package/rules/npm-module/js/docs/skill_meta.md +0 -2
- package/rules/npm-module/main.mdc +1 -15
- package/rules/php/docs/main.md +0 -2
- package/rules/php/js/docs/index.md +0 -1
- package/rules/php/main.mdc +1 -9
- package/rules/python/docs/main.md +0 -2
- package/rules/python/js/docs/index.md +0 -1
- package/rules/python/main.mdc +1 -13
- package/rules/python/policy/lint_python_yml/lint_python_yml.rego +9 -0
- package/rules/python/policy/pyproject_toml/pyproject_toml.rego +15 -1
- package/rules/rego/docs/main.md +0 -2
- package/rules/rego/js/docs/index.md +2 -2
- package/rules/rego/js/docs/tooling.md +0 -2
- package/rules/rego/js/tooling.mdc +14 -0
- package/rules/rego/main.mdc +0 -9
- package/rules/rego/policy/package_json/package_json.mdc +12 -0
- package/rules/release/docs/main.md +0 -2
- package/rules/release/main.mdc +2 -2
- package/rules/rust/docs/main.md +0 -2
- package/rules/rust/js/docs/index.md +0 -1
- package/rules/rust/main.mdc +1 -11
- package/rules/rust/policy/package_json/package_json.mdc +12 -0
- package/rules/security/docs/main.md +0 -2
- package/rules/security/js/docs/index.md +0 -1
- package/rules/security/main.mdc +0 -13
- package/rules/style/docs/main.md +0 -2
- package/rules/style/js/docs/index.md +2 -2
- package/rules/style/js/docs/tooling.md +0 -2
- package/rules/style/main.mdc +1 -23
- package/rules/tauri/docs/main.md +0 -2
- package/rules/tauri/main.mdc +1 -11
- package/rules/test/docs/main.md +0 -2
- package/rules/test/js/docs/no-console-store-restore.md +0 -2
- package/rules/test/js/docs/sandbox-aware-test.md +0 -2
- package/rules/test/js/docs/stryker_config.md +0 -2
- package/rules/test/js/docs/vitest-config-pool-forks.md +0 -2
- package/rules/test/main.mdc +1 -21
- package/rules/text/docs/main.md +0 -2
- package/rules/text/js/docs/cspell-fix.md +0 -2
- package/rules/text/js/docs/run-dotenv-linter.md +0 -2
- package/rules/text/js/docs/run-shellcheck.md +0 -2
- package/rules/text/js/docs/run-v8r.md +0 -2
- package/rules/text/main.mdc +0 -33
- package/rules/tool-surface/docs/main.md +0 -2
- package/rules/vue/docs/main.md +0 -2
- package/rules/vue/js/docs/packages.md +12 -17
- package/rules/vue/js/packages.mjs +41 -1
- package/rules/vue/main.mdc +0 -22
- package/rules/worktree/docs/main.md +0 -2
- package/scripts/docs/auto-rules.md +0 -2
- package/scripts/docs/auto-skills.md +0 -2
- package/scripts/docs/hook.md +13 -12
- package/scripts/docs/post-tool-use-check.md +0 -2
- package/scripts/docs/sync-claude-config.md +1 -3
- package/scripts/docs/sync-setup-bun-deps-action.md +0 -2
- package/scripts/hook.mjs +3 -4
- package/scripts/lib/docs/check-mdc-template-refs.md +0 -2
- package/scripts/lib/docs/index.md +35 -35
- package/scripts/lib/docs/inline-template-links.md +6 -8
- package/scripts/lib/docs/list-project-rules-mdc.md +0 -2
- package/scripts/lib/docs/list-rule-ids.md +0 -2
- package/scripts/lib/docs/mirror-parity.md +8 -10
- package/scripts/lib/docs/read-n-cursor-config-lite.md +0 -2
- package/scripts/lib/docs/rule-meta.md +0 -2
- package/scripts/lib/docs/run-lint.md +0 -2
- package/scripts/lib/docs/run-rule-cli.md +0 -2
- package/scripts/lib/docs/run-rule.md +0 -2
- package/scripts/lib/docs/run-standard-lint.md +0 -2
- package/scripts/lib/docs/run-standard-rule.md +0 -2
- package/scripts/lib/docs/skill-meta.md +0 -2
- package/scripts/lib/docs/timing-summary.md +0 -2
- package/scripts/lib/docs/worktree-notice.md +0 -2
- package/scripts/lib/fix/docs/analyze-escalation.md +0 -2
- package/scripts/lib/fix/docs/index.md +1 -0
- package/scripts/lib/fix/docs/llm-worker.md +18 -8
- package/scripts/lib/fix/docs/orchestrator.md +10 -16
- package/scripts/lib/fix/docs/run-conformance-check.md +0 -2
- package/scripts/lib/fix/docs/t0.md +0 -2
- package/scripts/lib/fix/docs/verbose-block.md +27 -0
- package/scripts/lib/fix/llm-worker.mjs +75 -22
- package/scripts/lib/fix/orchestrator.mjs +9 -3
- package/scripts/lib/fix/verbose-block.mjs +82 -0
- package/scripts/lib/inline-template-links.mjs +32 -22
- package/scripts/lib/mirror-parity.mjs +2 -2
- package/scripts/sync-claude-config.mjs +7 -4
- package/scripts/utils/docs/resolve-js-root.md +0 -2
- package/skills/doc-files/SKILL.md +9 -24
- package/skills/llm-patch/SKILL.md +4 -4
|
@@ -4,7 +4,7 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { env } from 'node:process'
|
|
6
6
|
import { resolveModel } from '../../../lib/models.mjs'
|
|
7
|
-
import {
|
|
7
|
+
import { callLlmRich } from '../../../lib/llm.mjs'
|
|
8
8
|
import { applyChanges, parseChangesResponse, readFilesForFix } from './llm-fix-apply.mjs'
|
|
9
9
|
|
|
10
10
|
// Дефолтна модель, коли викликач не задав `opts.model` (legacy/прямі виклики).
|
|
@@ -12,6 +12,10 @@ import { applyChanges, parseChangesResponse, readFilesForFix } from './llm-fix-a
|
|
|
12
12
|
// тут лишається тільки fallback на min-тир. Перевизначення — `N_CURSOR_FIX_MODEL`.
|
|
13
13
|
const MODEL = env.N_CURSOR_FIX_MODEL ?? resolveModel('min')
|
|
14
14
|
|
|
15
|
+
// Бюджет thinking-токенів для omlx-моделей (Gemma 4 та ін., що підтримують thinking_budget).
|
|
16
|
+
// Значення 0 вимикає thinking. Перевизначення — `N_CURSOR_OMLX_THINKING_BUDGET`.
|
|
17
|
+
const DEFAULT_THINKING_BUDGET = Number(env.N_CURSOR_OMLX_THINKING_BUDGET ?? 4096)
|
|
18
|
+
|
|
15
19
|
const API_KEY_RE = /api key/i
|
|
16
20
|
|
|
17
21
|
/**
|
|
@@ -112,23 +116,32 @@ function buildPrompt(ruleId, ruleMdc, output, files, feedback = null) {
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
/**
|
|
115
|
-
* Викликає LLM через
|
|
119
|
+
* Викликає LLM через `callLlmRich` (маршрут за префіксом model-id; wire-trace).
|
|
120
|
+
* Повертає reasoning поряд із текстом — для verbose-блоку оркестратора.
|
|
116
121
|
* Зберігає дружнє повідомлення про відсутній API-ключ для хмарних провайдерів.
|
|
117
122
|
* @param {string} prompt текст промпта
|
|
118
123
|
* @param {string} model назва моделі (provider/id, `omlx/...` або '')
|
|
119
124
|
* @param {string} caller мітка викликача для wire-trace (`fix:<rule>:<rung>`)
|
|
120
|
-
* @param {number} [timeoutMs] ліміт виклику (драбина задає per-tier; undefined → дефолт
|
|
121
|
-
* @
|
|
125
|
+
* @param {number} [timeoutMs] ліміт виклику (драбина задає per-tier; undefined → дефолт callLlmRich)
|
|
126
|
+
* @param {number} [thinkingBudget] бюджет thinking-токенів (лише omlx; 0 = вимкнено)
|
|
127
|
+
* @returns {{ text: string, reasoning: string|null, reasoningSource: string|null, error?: string }}
|
|
122
128
|
*/
|
|
123
|
-
function callModel(prompt, model, caller, timeoutMs) {
|
|
129
|
+
function callModel(prompt, model, caller, timeoutMs, thinkingBudget) {
|
|
124
130
|
try {
|
|
125
|
-
|
|
131
|
+
const { content, reasoning, reasoningSource } = callLlmRich([{ role: 'user', content: prompt }], model, {
|
|
132
|
+
timeoutMs,
|
|
133
|
+
caller,
|
|
134
|
+
thinkingBudget
|
|
135
|
+
})
|
|
136
|
+
return { text: content, reasoning, reasoningSource }
|
|
126
137
|
} catch (error) {
|
|
127
138
|
const msg = String(error.message)
|
|
128
139
|
if (API_KEY_RE.test(msg)) {
|
|
129
140
|
const provider = model ? model.split('/')[0] : 'дефолтного провайдера'
|
|
130
141
|
return {
|
|
131
142
|
text: '',
|
|
143
|
+
reasoning: null,
|
|
144
|
+
reasoningSource: null,
|
|
132
145
|
error: [
|
|
133
146
|
`pi: немає ключа для ${provider}.`,
|
|
134
147
|
`Встановіть N_CLOUD_MIN_MODEL=provider/model-id`,
|
|
@@ -136,7 +149,7 @@ function callModel(prompt, model, caller, timeoutMs) {
|
|
|
136
149
|
].join(' ')
|
|
137
150
|
}
|
|
138
151
|
}
|
|
139
|
-
return { text: '', error: msg }
|
|
152
|
+
return { text: '', reasoning: null, reasoningSource: null, error: msg }
|
|
140
153
|
}
|
|
141
154
|
}
|
|
142
155
|
|
|
@@ -144,21 +157,24 @@ function callModel(prompt, model, caller, timeoutMs) {
|
|
|
144
157
|
* LLM-worker: виправляє одне rule-порушення через pi (C1 pattern).
|
|
145
158
|
* Повертає `changes`/`diagnosis` навіть при невдачі — драбина ескалації
|
|
146
159
|
* (`orchestrator.mjs`) логує їх і прокидає як feedback у наступний рунг.
|
|
160
|
+
* Поля `reasoning`/`reasoningSource`/`promptSummary` використовує оркестратор
|
|
161
|
+
* для verbose-блоку після кожного рунга (`--full` режим).
|
|
147
162
|
* @param {string} ruleId ID правила
|
|
148
163
|
* @param {string} violationOutput output з fix check для цього rule
|
|
149
164
|
* @param {string} projectRoot абсолютний шлях до кореня проєкту
|
|
150
|
-
* @param {{ model?: string, feedback?: object|null, caller?: string, timeoutMs?: number }} opts опції:
|
|
165
|
+
* @param {{ model?: string, feedback?: object|null, caller?: string, timeoutMs?: number, thinkingBudget?: number }} opts опції:
|
|
151
166
|
* `model` — перевизначення моделі; `feedback` — контекст попереднього рунга
|
|
152
167
|
* драбини (retry-with-feedback); `caller` — мітка для wire-trace; `timeoutMs` —
|
|
153
|
-
* per-tier ліміт виклику (драбина: локалі fail-fast, хмара повний)
|
|
154
|
-
*
|
|
155
|
-
*
|
|
168
|
+
* per-tier ліміт виклику (драбина: локалі fail-fast, хмара повний);
|
|
169
|
+
* `thinkingBudget` — кількість thinking-токенів для omlx (дефолт `DEFAULT_THINKING_BUDGET`)
|
|
170
|
+
* @returns {{ ok: boolean, error?: string, changes: Array<{path:string}>, diagnosis: string|null, reasoning: string|null, reasoningSource: string|null, promptSummary: object }}
|
|
156
171
|
*/
|
|
157
172
|
export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
|
|
158
173
|
const model = opts.model ?? MODEL
|
|
159
174
|
const feedback = opts.feedback ?? null
|
|
160
175
|
const caller = opts.caller ?? 'fix'
|
|
161
176
|
const timeoutMs = opts.timeoutMs
|
|
177
|
+
const thinkingBudget = opts.thinkingBudget ?? DEFAULT_THINKING_BUDGET
|
|
162
178
|
|
|
163
179
|
// 1. Читаємо rule .mdc
|
|
164
180
|
const mdcPath = join(projectRoot, '.cursor', 'rules', `n-${ruleId}.mdc`)
|
|
@@ -167,24 +183,61 @@ export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
|
|
|
167
183
|
// 2. Витягуємо файли з violation output і читаємо їх
|
|
168
184
|
const files = readFilesForFix(extractFilePaths(violationOutput), projectRoot)
|
|
169
185
|
|
|
170
|
-
// 3. Будуємо
|
|
171
|
-
const
|
|
172
|
-
|
|
186
|
+
// 3. Будуємо summary промпту (для verbose-блоку) до виклику моделі
|
|
187
|
+
const promptSummary = {
|
|
188
|
+
ruleMdcLen: ruleMdc.length,
|
|
189
|
+
violationLen: violationOutput.length,
|
|
190
|
+
filesCount: files.length,
|
|
191
|
+
filesTotalBytes: files.reduce((s, f) => s + f.content.length, 0),
|
|
192
|
+
hasFeedback: !!feedback,
|
|
193
|
+
feedbackModel: feedback?.previousModel ?? null,
|
|
194
|
+
feedbackChangesCount: feedback?.previousChanges?.length ?? 0,
|
|
195
|
+
feedbackError: feedback?.previousError ?? null
|
|
196
|
+
}
|
|
173
197
|
|
|
174
|
-
|
|
175
|
-
|
|
198
|
+
// 4. Будуємо prompt і викликаємо модель
|
|
199
|
+
const prompt = buildPrompt(ruleId, ruleMdc, violationOutput, files, feedback)
|
|
200
|
+
const {
|
|
201
|
+
text,
|
|
202
|
+
error: modelError,
|
|
203
|
+
reasoning,
|
|
204
|
+
reasoningSource
|
|
205
|
+
} = callModel(prompt, model, caller, timeoutMs, thinkingBudget)
|
|
206
|
+
|
|
207
|
+
if (modelError)
|
|
208
|
+
return { ok: false, error: modelError, changes: [], diagnosis: null, reasoning, reasoningSource, promptSummary }
|
|
209
|
+
if (!text)
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
error: 'model returned empty response',
|
|
213
|
+
changes: [],
|
|
214
|
+
diagnosis: null,
|
|
215
|
+
reasoning,
|
|
216
|
+
reasoningSource,
|
|
217
|
+
promptSummary
|
|
218
|
+
}
|
|
176
219
|
|
|
177
|
-
//
|
|
220
|
+
// 5. Парсимо відповідь
|
|
178
221
|
const parsed = parseChangesResponse(text)
|
|
179
222
|
if (!parsed) {
|
|
180
|
-
return {
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
error: `cannot parse pi response: ${text.slice(0, 200)}`,
|
|
226
|
+
changes: [],
|
|
227
|
+
diagnosis: null,
|
|
228
|
+
reasoning,
|
|
229
|
+
reasoningSource,
|
|
230
|
+
promptSummary
|
|
231
|
+
}
|
|
181
232
|
}
|
|
182
233
|
const diagnosis = typeof parsed.diagnosis === 'string' && parsed.diagnosis ? parsed.diagnosis : null
|
|
183
234
|
const changes = parsed.changes ?? []
|
|
184
|
-
if (parsed.error)
|
|
185
|
-
|
|
235
|
+
if (parsed.error)
|
|
236
|
+
return { ok: false, error: parsed.error, changes, diagnosis, reasoning, reasoningSource, promptSummary }
|
|
237
|
+
if (changes.length === 0)
|
|
238
|
+
return { ok: false, error: 'pi returned no changes', changes, diagnosis, reasoning, reasoningSource, promptSummary }
|
|
186
239
|
|
|
187
|
-
//
|
|
240
|
+
// 6. Застосовуємо зміни
|
|
188
241
|
const applied = applyChanges(changes, projectRoot)
|
|
189
|
-
return { ...applied, changes, diagnosis }
|
|
242
|
+
return { ...applied, changes, diagnosis, reasoning, reasoningSource, promptSummary }
|
|
190
243
|
}
|
|
@@ -5,6 +5,7 @@ import { runConformanceCheck } from './run-conformance-check.mjs'
|
|
|
5
5
|
import { runT0AutoCli } from './t0.mjs'
|
|
6
6
|
import { logEscalation } from './escalation-log.mjs'
|
|
7
7
|
import { runLlmWorker } from './llm-worker.mjs'
|
|
8
|
+
import { printVerboseBlock } from './verbose-block.mjs'
|
|
8
9
|
import { classifyOmlxError } from '../../../lib/llm.mjs'
|
|
9
10
|
import { CLOUD_AVG, CLOUD_MIN, LOCAL_MIN } from '../../../lib/models.mjs'
|
|
10
11
|
|
|
@@ -147,11 +148,16 @@ export async function escalateRule(rule, cwd, deps) {
|
|
|
147
148
|
|
|
148
149
|
if (recheckOk) {
|
|
149
150
|
log(` ✅ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}`)
|
|
150
|
-
|
|
151
|
+
} else {
|
|
152
|
+
const hint = res.error ? ` ❌ ${res.error.slice(0, 120)}` : ' ❌ досі порушено'
|
|
153
|
+
log(` ⚡ ${rung.tier} (${rung.model || 'pi'}): ${rule.ruleId}${hint}`)
|
|
151
154
|
}
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
if (env.N_CURSOR_FIX_VERBOSE !== 'off' && res.promptSummary) {
|
|
157
|
+
printVerboseBlock(rule.ruleId, res.promptSummary, res.reasoning ?? null, res.reasoningSource ?? null)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (recheckOk) return { resolved: true, avgUsed }
|
|
155
161
|
|
|
156
162
|
// Feedback для наступного рунга + оновлений violation.
|
|
157
163
|
feedback = { previousModel: rung.model, previousChanges: res.changes ?? [], previousError: res.error ?? null }
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verbose-блок після кожного LLM-рунга у `--full` режимі.
|
|
3
|
+
* Друкує стислий опис промпту і thinking-монолог моделі (якщо є).
|
|
4
|
+
* Вимикається через `N_CURSOR_FIX_VERBOSE=off`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const THINKING_PREVIEW_LEN = 500
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Форматує рядок файлів для prompt-блоку.
|
|
11
|
+
* @param {number} count кількість файлів
|
|
12
|
+
* @param {number} totalBytes сумарний розмір у байтах
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function formatFiles(count, totalBytes) {
|
|
16
|
+
if (count === 0) return '(none)'
|
|
17
|
+
const kb = (totalBytes / 1024).toFixed(1)
|
|
18
|
+
const word = count === 1 ? 'файл' : count < 5 ? 'файли' : 'файлів'
|
|
19
|
+
return `${count} ${word} (${kb} KB)`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Форматує рядок feedback для prompt-блоку.
|
|
24
|
+
* @param {boolean} hasFeedback чи є feedback
|
|
25
|
+
* @param {string|null} feedbackModel модель попереднього рунга
|
|
26
|
+
* @param {number} feedbackChangesCount кількість змін попереднього рунга
|
|
27
|
+
* @param {string|null} feedbackError помилка попереднього рунга
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function formatFeedback(hasFeedback, feedbackModel, feedbackChangesCount, feedbackError) {
|
|
31
|
+
if (!hasFeedback) return '(none)'
|
|
32
|
+
const parts = []
|
|
33
|
+
if (feedbackModel) parts.push(`model=${feedbackModel}`)
|
|
34
|
+
parts.push(`${feedbackChangesCount} changes`)
|
|
35
|
+
if (feedbackError) parts.push(`error="${feedbackError.slice(0, 60)}"`)
|
|
36
|
+
return parts.join(', ')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Друкує verbose-блок після рядка рунга (`⚡`/`✅`).
|
|
41
|
+
* Містить стислий опис промпту і thinking-монолог моделі (якщо є).
|
|
42
|
+
* Не викликається якщо `N_CURSOR_FIX_VERBOSE=off`.
|
|
43
|
+
* @param {string} ruleId ID правила
|
|
44
|
+
* @param {{ ruleMdcLen: number, violationLen: number, filesCount: number, filesTotalBytes: number, hasFeedback: boolean, feedbackModel: string|null, feedbackChangesCount: number, feedbackError: string|null }} promptSummary стислий опис промпту
|
|
45
|
+
* @param {string|null} reasoning thinking-монолог моделі
|
|
46
|
+
* @param {string|null} reasoningSource джерело reasoning ('field'|'think_tag'|'truncated'|null)
|
|
47
|
+
*/
|
|
48
|
+
export function printVerboseBlock(ruleId, promptSummary, reasoning, reasoningSource) {
|
|
49
|
+
const {
|
|
50
|
+
ruleMdcLen,
|
|
51
|
+
violationLen,
|
|
52
|
+
filesCount,
|
|
53
|
+
filesTotalBytes,
|
|
54
|
+
hasFeedback,
|
|
55
|
+
feedbackModel,
|
|
56
|
+
feedbackChangesCount,
|
|
57
|
+
feedbackError
|
|
58
|
+
} = promptSummary
|
|
59
|
+
|
|
60
|
+
console.log(``)
|
|
61
|
+
console.log(` prompt:`)
|
|
62
|
+
console.log(` rule: n-${ruleId}.mdc (${ruleMdcLen} chars)`)
|
|
63
|
+
console.log(` violation: ${violationLen} chars`)
|
|
64
|
+
console.log(` files: ${formatFiles(filesCount, filesTotalBytes)}`)
|
|
65
|
+
console.log(` feedback: ${formatFeedback(hasFeedback, feedbackModel, feedbackChangesCount, feedbackError)}`)
|
|
66
|
+
|
|
67
|
+
if (reasoning) {
|
|
68
|
+
const preview =
|
|
69
|
+
reasoning.length > THINKING_PREVIEW_LEN
|
|
70
|
+
? reasoning.slice(0, THINKING_PREVIEW_LEN) + ` … (+${reasoning.length - THINKING_PREVIEW_LEN} chars)`
|
|
71
|
+
: reasoning
|
|
72
|
+
console.log(``)
|
|
73
|
+
console.log(` thinking [${reasoningSource}, ${reasoning.length} chars]:`)
|
|
74
|
+
for (const line of preview.split('\n')) {
|
|
75
|
+
console.log(` ${line}`)
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
console.log(``)
|
|
79
|
+
console.log(` thinking: (none)`)
|
|
80
|
+
}
|
|
81
|
+
console.log(``)
|
|
82
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
|
-
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { readFile, readdir } from 'node:fs/promises'
|
|
3
3
|
import { basename, extname, join } from 'node:path'
|
|
4
4
|
|
|
5
5
|
const MD_LINK_RE = /\[([^\]]{1,200})\]\((\.\/[^)]{1,500})\)/g
|
|
6
6
|
const TEMPLATE_SEGMENT_RE = /\/templates?\//
|
|
7
|
-
const MDC_EXT_RE = /\.mdc$/
|
|
8
7
|
/** Статичні regexp-літерали `^(.+)\.<slot>\.<ext>$` — без `RegExp(variable)`. */
|
|
9
8
|
const SLOT_SUFFIX_RES = [/^(.+)\.snippet\.[^.]+$/, /^(.+)\.deny\.[^.]+$/, /^(.+)\.contains\.[^.]+$/]
|
|
10
9
|
|
|
@@ -69,31 +68,42 @@ export async function inlineTemplateLinks(text, ruleDir) {
|
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* @param {string} text .mdc file contents (after inlineTemplateLinks)
|
|
71
|
+
* Appends all *.mdc files auto-discovered in js/ and policy/<concern>/ subdirectories.
|
|
72
|
+
* js/ direct files come first (alphabetically), then policy concern directories
|
|
73
|
+
* (alphabetically by concern name, then by file name within each concern).
|
|
74
|
+
* @param {string} text rule content (after inlineTemplateLinks)
|
|
77
75
|
* @param {string} ruleDir absolute path to the rule directory
|
|
78
|
-
* @returns {Promise<string>}
|
|
76
|
+
* @returns {Promise<string>} text with discovered concern docs appended
|
|
79
77
|
*/
|
|
80
|
-
export async function
|
|
81
|
-
const
|
|
82
|
-
if (matches.length === 0) return text
|
|
78
|
+
export async function appendDiscoveredMdcFiles(text, ruleDir) {
|
|
79
|
+
const sections = []
|
|
83
80
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
throw new Error(`inlineMarkdownIncludes: file not found: ${absPath} (referenced from .mdc)`)
|
|
81
|
+
const jsDir = join(ruleDir, 'js')
|
|
82
|
+
if (existsSync(jsDir)) {
|
|
83
|
+
const entries = await readdir(jsDir, { withFileTypes: true })
|
|
84
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
85
|
+
if (e.isFile() && e.name.endsWith('.mdc')) {
|
|
86
|
+
sections.push((await readFile(join(jsDir, e.name), 'utf8')).trim())
|
|
87
|
+
}
|
|
92
88
|
}
|
|
89
|
+
}
|
|
93
90
|
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
const policyDir = join(ruleDir, 'policy')
|
|
92
|
+
if (existsSync(policyDir)) {
|
|
93
|
+
const concerns = (await readdir(policyDir, { withFileTypes: true }))
|
|
94
|
+
.filter(e => e.isDirectory())
|
|
95
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
96
|
+
for (const concern of concerns) {
|
|
97
|
+
const concernDir = join(policyDir, concern.name)
|
|
98
|
+
const files = (await readdir(concernDir, { withFileTypes: true }))
|
|
99
|
+
.filter(e => e.isFile() && e.name.endsWith('.mdc'))
|
|
100
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
101
|
+
for (const f of files) {
|
|
102
|
+
sections.push((await readFile(join(concernDir, f.name), 'utf8')).trim())
|
|
103
|
+
}
|
|
104
|
+
}
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
return
|
|
107
|
+
if (sections.length === 0) return text
|
|
108
|
+
return text.trimEnd() + '\n\n' + sections.join('\n\n') + '\n'
|
|
99
109
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
10
10
|
import { dirname, join } from 'node:path'
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { appendDiscoveredMdcFiles, inlineTemplateLinks } from './inline-template-links.mjs'
|
|
13
13
|
|
|
14
14
|
const MIRROR_PREFIX = 'n-'
|
|
15
15
|
const MDC_EXT = '.mdc'
|
|
@@ -44,7 +44,7 @@ export function listManagedMirrors(repoRoot) {
|
|
|
44
44
|
export async function expectedMirrorContent(canonicalPath) {
|
|
45
45
|
const dir = dirname(canonicalPath)
|
|
46
46
|
const withTemplates = await inlineTemplateLinks(readFileSync(canonicalPath, 'utf8'), dir)
|
|
47
|
-
return
|
|
47
|
+
return appendDiscoveredMdcFiles(withTemplates, dir)
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -30,11 +30,13 @@ import { existsSync } from 'node:fs'
|
|
|
30
30
|
import { chmod, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'
|
|
31
31
|
import { join } from 'node:path'
|
|
32
32
|
|
|
33
|
-
/** Маркер
|
|
34
|
-
export const MANAGED_HOOK_COMMAND_MARKER = '@nitra/cursor
|
|
35
|
-
/**
|
|
33
|
+
/** Маркер hook-ів пакета (`hook --post-tool-use`, `hook --stop`). */
|
|
34
|
+
export const MANAGED_HOOK_COMMAND_MARKER = '@nitra/cursor hook'
|
|
35
|
+
/** @deprecated — замінено на `hook --post-tool-use`; маркер лишається для cleanup наявних конфігів при ресинку. */
|
|
36
|
+
export const LEGACY_POST_TOOL_USE_HOOK_COMMAND_MARKER = '@nitra/cursor post-tool-use-check'
|
|
37
|
+
/** @deprecated — doc-files hook перенесено до `hook --post-tool-use`; маркер лишається для cleanup наявних конфігів при ресинку. */
|
|
36
38
|
export const DOC_FILES_HOOK_COMMAND_MARKER = '@nitra/cursor lint-doc-files'
|
|
37
|
-
/**
|
|
39
|
+
/** @deprecated — ще старіший legacy-маркер doc-files hook'ів (`doc-files check`) — cleanup при ресинку. */
|
|
38
40
|
export const LEGACY_DOC_FILES_HOOK_COMMAND_MARKER = '@nitra/cursor doc-files check'
|
|
39
41
|
/** Legacy-маркер старого Stop-hook'а — лишаємо для cleanup-у при оновленні існуючих інсталяцій. */
|
|
40
42
|
export const LEGACY_STOP_HOOK_COMMAND_MARKER = '@nitra/cursor stop-hook'
|
|
@@ -52,6 +54,7 @@ export const CURSOR_ADR_NORMALIZE_HOOK_COMMAND_MARKER = '.claude/hooks/normalize
|
|
|
52
54
|
*/
|
|
53
55
|
export const MANAGED_HOOK_COMMAND_MARKERS = Object.freeze([
|
|
54
56
|
MANAGED_HOOK_COMMAND_MARKER,
|
|
57
|
+
LEGACY_POST_TOOL_USE_HOOK_COMMAND_MARKER,
|
|
55
58
|
DOC_FILES_HOOK_COMMAND_MARKER,
|
|
56
59
|
LEGACY_DOC_FILES_HOOK_COMMAND_MARKER,
|
|
57
60
|
LEGACY_STOP_HOOK_COMMAND_MARKER,
|
|
@@ -8,8 +8,6 @@ docgen:
|
|
|
8
8
|
score: 100
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
## Огляд
|
|
12
|
-
|
|
13
11
|
Визначає корінь JS-коду в проєкті, відповідно до логіки для workspace-projects (перший workspace з підтримкою glob-патернів `cf/*`) або single-package (корінь cwd). Ця утиліта є спільною для coverage-провайдера JS та test-концерну `stryker_config` (DRY). Публічні функції дозволяють отримати один або повний список шляхів до всіх JS-коренів, при цьому свідомо виключаються каталоги `.git` та `node_modules`. Код спирається на конфігураційні файли `package.json` та `.n-cursor.json`.
|
|
14
12
|
|
|
15
13
|
## Поведінка
|
|
@@ -31,8 +31,8 @@ docgen:
|
|
|
31
31
|
|
|
32
32
|
## Оркестрацію веде JS, не модель; конвеєр — local-only
|
|
33
33
|
|
|
34
|
-
Уся важка робота — черга, батчинг, виклики LLM і штамп CRC — живе
|
|
35
|
-
`
|
|
34
|
+
Уся важка робота — черга, батчинг, виклики LLM і штамп CRC — живе у JS-оркестрації
|
|
35
|
+
`lint doc-files`. **Ти не диспатчиш субагентів і не тримаєш сотні файлів у контексті**
|
|
36
36
|
— тому навіть масовий перший прогін усього репо не «заморює». Цей скіл **тонкий**: твоє завдання —
|
|
37
37
|
запустити генерацію і прочитати підсумок.
|
|
38
38
|
|
|
@@ -51,34 +51,20 @@ docgen:
|
|
|
51
51
|
### Крок 1: Генерація застарілих/відсутніх док
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
npx @nitra/cursor
|
|
54
|
+
npx @nitra/cursor lint doc-files
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
Команда
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- Для дуже великого прогону можна порціями: `--from N --limit M`.
|
|
63
|
-
- Перегенерувати **всі** доки (не лише застарілі): `--overwrite`.
|
|
64
|
-
- Перегенерувати лише degraded (свіжі за CRC, score < порогу): `--retry-degraded`.
|
|
57
|
+
Команда запускає doc-files лінтер у fix-режимі (весь репо): перевіряє omlx
|
|
58
|
+
(preflight: «сервер лежить» / «модель не влазить у пам'ять зайнятої машини» /
|
|
59
|
+
«потрібен API-ключ» → одна зрозуміла зупинка замість лавини «✗») → фільтрує застарілі
|
|
60
|
+
(`stale`) → генерує локальною моделлю → пише доку зі **свіжим CRC** (і degraded-маркером,
|
|
61
|
+
якщо не дотягнула) → друкує прогрес і підсумок.
|
|
65
62
|
|
|
66
63
|
### Крок 2: Підтвердження
|
|
67
64
|
|
|
68
65
|
Дочекайся підсумку `✓ OK: <N> ⚠ degraded: <D> ✗ Err: <E>`. Якщо є помилки — перелічи
|
|
69
66
|
проблемні файли. Exit-код `1` означає, що хоча б один файл не згенерувався (або не пройшов
|
|
70
|
-
preflight). Degraded — не помилка: дока існує,
|
|
71
|
-
|
|
72
|
-
### Крок 3 (за потреби): перевірка перед завершенням
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
npx @nitra/cursor lint-doc-files --git
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Перевіряє змінені у задачі джерела (`git diff --name-only HEAD`) проти CRC їхніх док.
|
|
79
|
-
Цю ж перевірку виконує **Stop-hook** як твердий гейт: завершити задачу зі застарілими
|
|
80
|
-
доками не можна (виняток — масовий прогін понад поріг `N_CURSOR_DOC_FILES_GATE_MAX`, дефолт 50).
|
|
81
|
-
Degraded-доки гейт **не** блокує (CRC свіжий); їх список — `lint-doc-files --degraded`.
|
|
67
|
+
preflight). Degraded — не помилка: дока існує, CRC свіжий.
|
|
82
68
|
|
|
83
69
|
## Правила стилю документа (за adr/ci4)
|
|
84
70
|
|
|
@@ -98,4 +84,3 @@ Degraded-доки гейт **не** блокує (CRC свіжий); їх спи
|
|
|
98
84
|
усі теки `docs/`, а також `*.test.*` / `*.spec.*` / `*.d.ts`. Кореневий repo `docs/` —
|
|
99
85
|
system-wide only: file-level docs туди не пишуться. Список glob-ів — `docgen-ignore.mjs`.
|
|
100
86
|
- Агрегуюча документація (module-summary, доменні доки) — окремий скіл `doc-aggregate`, за запитом.
|
|
101
|
-
- Для наявних док без CRC одноразово: `npx @nitra/cursor fix-doc-files --stamp` (штампує frontmatter без LLM).
|
|
@@ -74,12 +74,12 @@ version: '1.0'
|
|
|
74
74
|
inflight-міграції, breaking-change політика, зовнішні залежності.
|
|
75
75
|
- **Change-file flow,** якщо завдання змінює файли у пакетному workspace (код,
|
|
76
76
|
правила, скіли, конфіги, тести — не лише `docs/`): промпт має вимагати
|
|
77
|
-
`npx @
|
|
77
|
+
`npx @7n/n ch [--bump <major|minor|patch>] [--section <Added|Changed|Fixed|Removed>] [--message "<опис>"]`
|
|
78
78
|
і `npx @nitra/cursor fix changelog`. **Ніколи** не інструктуй ручне редагування
|
|
79
79
|
`CHANGELOG.md` чи bump `version` — це робить release flow / CI (деталь —
|
|
80
80
|
`.cursor/rules/n-changelog.mdc`, не дублюй її текст). Якщо потрібна реліз-нота —
|
|
81
81
|
це change-файл `<ws>/.changes/<timestamp>-<rand>.md` з `bump:` і `section:`,
|
|
82
|
-
який створює
|
|
82
|
+
який створює `@7n/n ch`, а не редагування файлу вручну.
|
|
83
83
|
- **Як перевірити** — конкретні команди й специфічні до завдання сигнали
|
|
84
84
|
успіху.
|
|
85
85
|
|
|
@@ -197,7 +197,7 @@ version: '1.0'
|
|
|
197
197
|
- **Без ручного changelog/version у промпті:** не формулюй у виводі інструкції
|
|
198
198
|
на кшталт "додати запис у `CHANGELOG.md`", "bump `version` вручну" чи
|
|
199
199
|
"оновити `package.json#version`". Зміни у workspace фіксуються винятково
|
|
200
|
-
через change-file flow (`npx @
|
|
200
|
+
через change-file flow (`npx @7n/n ch` → `npx @nitra/cursor fix changelog`);
|
|
201
201
|
`version`/`CHANGELOG.md` формує CI.
|
|
202
202
|
- **Не вмикай у промпт:** секрети, `.env`, `node_modules`, бінарні файли,
|
|
203
203
|
довгі логи, дампи `tree`, повні JSON конфігів, цитати існуючих
|
|
@@ -239,7 +239,7 @@ version: '1.0'
|
|
|
239
239
|
- Підняти `engines.node` до `>=25`; якщо peer `eslint ^9` несумісний —
|
|
240
240
|
підняти range.
|
|
241
241
|
- Зафіксувати зміну change-файлом (НЕ редагувати `CHANGELOG.md` чи `version`
|
|
242
|
-
вручну): `npx @
|
|
242
|
+
вручну): `npx @7n/n ch --bump minor --section Changed --message "engines.node >=25"`.
|
|
243
243
|
|
|
244
244
|
# Обмеження
|
|
245
245
|
|