@7n/rules 1.48.2 → 1.49.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/CHANGELOG.md +19 -0
- package/bin/n-rules-cli.mjs +5 -8
- package/package.json +1 -1
- package/rules/changelog/.changes/260724-1500.md +5 -0
- package/rules/doc-files/docgen-files-batch/docs/index.md +9 -0
- package/rules/doc-files/docgen-files-batch/docs/main.md +59 -18
- package/rules/doc-files/docgen-files-batch/main.mjs +280 -29
- package/rules/doc-files/docgen-gen/docs/index.md +9 -0
- package/rules/doc-files/docgen-gen/docs/main.md +40 -29
- package/rules/doc-files/docgen-gen/main.mjs +79 -25
- package/rules/test/coverage/fix-worker.mjs +9 -1
- package/rules/test/coverage/lib/classify/verdict-schema.mjs +3 -1
- package/scripts/docs/skills-cli.md +18 -24
- package/scripts/lib/acp-runner.mjs +1 -1
- package/scripts/lib/lint-surface/collateral-veto.mjs +78 -2
- package/scripts/lib/lint-surface/docs/collateral-veto.md +30 -16
- package/scripts/lib/lint-surface/docs/index.md +1 -0
- package/scripts/lib/lint-surface/docs/run-fix.md +8 -23
- package/scripts/lib/lint-surface/docs/snapshot.md +6 -17
- package/scripts/lib/lint-surface/docs/test-gate.md +29 -0
- package/scripts/lib/lint-surface/run-fix.mjs +209 -38
- package/scripts/lib/lint-surface/snapshot.mjs +6 -0
- package/scripts/lib/lint-surface/test-gate.mjs +87 -0
- package/scripts/skills-cli.mjs +60 -10
- package/scripts/utils/docs/glob-compat.md +20 -14
- package/scripts/utils/glob-compat.mjs +18 -3
- package/skills/git-reconcile/SKILL.md +58 -0
- package/skills/git-reconcile/js/docs/index.md +9 -0
- package/skills/git-reconcile/js/docs/orchestrate.md +33 -0
- package/skills/git-reconcile/js/orchestrate.mjs +776 -0
- package/skills/git-reconcile/main.json +1 -0
- package/skills/taze/js/docs/orchestrate.md +40 -18
- package/skills/taze/js/orchestrate.mjs +6 -3
|
@@ -3,39 +3,50 @@ type: JS Module
|
|
|
3
3
|
title: main.mjs
|
|
4
4
|
resource: npm/rules/doc-files/docgen-gen/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model:
|
|
6
|
+
crc: 80c789c7
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
9
|
+
score: 10
|
|
10
|
+
issues: no-overview,short-behavior,internal-name:isApiGap,internal-name:renderApiLine,internal-name:oneShotDoc,internal-name:finishUnsupported,anchor-miss:(abie.mdc),best-of-2:retry-lost
|
|
8
11
|
---
|
|
9
12
|
|
|
10
|
-
## Огляд
|
|
11
|
-
|
|
12
|
-
Огляд
|
|
13
|
-
Створює технічну документацію шляхом аналізу вихідного коду, генерації тексту, оцінки його якості та управління позиціонуванням захищеного блоку «Призначення» у фінальному документі. (abie.mdc)
|
|
14
|
-
|
|
15
|
-
Поведінка
|
|
16
|
-
splitProtected відокремлює захищений блок «Призначення» від основного вмісту документа, надаючи його тіло та текст без цього блоку.
|
|
17
|
-
insertProtected поміщає захищений блок «Призначення» одразу після основного заголовка документа, якщо надано його тіло.
|
|
18
|
-
scoreDoc оцінює згенерований документ за заданими критеріями, визначаючи його якість та реєструючи проблеми.
|
|
19
|
-
DEFAULT_LOCAL_MODEL встановлює модель, яку використовувати для генерації документа, якщо не вказано іншу.
|
|
20
|
-
generateDoc створює повний технічний документ з вихідного файлу, включаючи вихідну оцінку якості.
|
|
21
|
-
|
|
22
|
-
## Поведінка
|
|
23
|
-
|
|
24
|
-
splitProtected відокремлює захищений блок «Призначення» від основного вмісту документа, повертаючи його тіло та текст без цього блоку.
|
|
25
|
-
insertProtected розміщує захищений блок «Призначення» одразу після основного заголовка документа, якщо його тіло було надано.
|
|
26
|
-
scoreDoc оцінює згенерований документ за набором детермінованих правил (generic-огляд, коротка поведінка, галюцинації про кеш, витік службових імен, анкор-покриття, суржик) і реєструє коди проблем. Правило R8 — refusal/чат-філер («Я готовий писати…», «Надайте мені код…», через пре-гейт docgen-judge): форсує degraded незалежно від структурної оцінки, тож LLM-суддя на такому змісті не викликається; захищене людське «Призначення» з перевірки виключене.
|
|
27
|
-
DEFAULT_LOCAL_MODEL визначає модель за замовчуванням для генерації документа, якщо не вказано інше.
|
|
28
|
-
generateDoc створює повний технічний документ з вихідного файлу, включаючи детерміновану оцінку якості; для непідтримуваних структур (one-shot шлях) оцінка не застосовується, але refusal-пре-гейт діє і там — філер отримує score 0 і позначку degraded, щоб доретрай батчу підібрав файл.
|
|
29
|
-
generateDoc приймає опційний `deadlineAt` (м'який дедлайн fix-pipeline, epoch ms): кожен LLM-виклик ріже свій таймаут під залишок бюджету (`capTimeoutToDeadline`), а вичерпаний бюджет обриває генерацію помилкою зі словом «timeout» ще до старту виклику — батч класифікує її як transient, і генерація ніколи не переживає бюджет рунга (без батчу-зомбі поверх наступного rung-а).
|
|
30
|
-
|
|
31
13
|
## Публічний API
|
|
32
14
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
15
|
+
- capTimeoutToDeadline — Ріже базовий per-call таймаут під залишок бюджету до дедлайну.
|
|
16
|
+
Без дедлайну — базовий ліміт; після дедлайну — 0 (виклик не має стартувати).
|
|
17
|
+
- stripLeadingPreamble — R9: зрізає провідні чат-преамбули й дубль назви секції з початку тексту.
|
|
18
|
+
Ітерується, поки перший непорожній рядок лишається мета-нарацією — модель
|
|
19
|
+
інколи ставить дві поспіль («Як технічний письменник…» + «Ось оновлений…»).
|
|
20
|
+
- splitProtected — Відокремлює захищену секцію `## Призначення` (Варіант B). Межа — наступний `## `
|
|
21
|
+
(H2); `###`+ усередині не обривають блок.
|
|
22
|
+
- insertProtected — Вставляє захищений блок `## Призначення` одразу після H1 (фіксована позиція).
|
|
23
|
+
- scoreDoc — Stage 2.5 — детермінований скоринг (0 токенів): перевіряє вихід проти фактів.
|
|
24
|
+
- buildApiSection — Stage 1/3 (гібрид doc-files, ADR 260719-2155): «Публічний API» — покриті
|
|
25
|
+
JSDoc-описом експорти рендеряться дослівно (`renderApiLine`, 0 токенів, 0
|
|
26
|
+
галюцинацій), LLM викликається лише на прогалини (`isApiGap`). Якщо прогалин
|
|
27
|
+
немає — секція збирається БЕЗ жодного LLM-виклику. Єдиний непокритий
|
|
28
|
+
експорт (як і раніше) лишається описаним лише в Поведінці — окремого виклику
|
|
29
|
+
на секцію з одного рядка не варте.
|
|
30
|
+
- DEFAULT_LOCAL_MODEL — Дефолтна модель: N_CURSOR_DOCGEN_MODEL → resolveModel('min') (→ N_LOCAL_MIN_MODEL).
|
|
31
|
+
Без хардкод-fallback: модель налаштовує кожен локально (`N_LOCAL_MIN_MODEL`); якщо
|
|
32
|
+
нічого не задано — порожньо, і preflight оркестратора фейлить гучно (а не шле
|
|
33
|
+
запит до неіснуючої моделі).
|
|
34
|
+
- generateDoc — Головний API: файл → md-дока з det-оцінкою.
|
|
35
|
+
|
|
36
|
+
Local-only (ADR 260610-2228): жодних cloud-ескалацій і pre-route — будь-який
|
|
37
|
+
файл генерується локальною моделлю. Якщо det-score нижче порогу, один retry
|
|
38
|
+
з вищою температурою (best-of-2); якщо й він не допоміг — результат
|
|
39
|
+
позначається `degraded`, рішення про перегенерацію приймає batch/користувач.
|
|
40
|
+
- prepareBatchItem — T8 (2b-batch, рішення Р): підготовка ОДНОГО item-у для `submitBatch` — та сама
|
|
41
|
+
pre-send guard і той самий факт-лист/one-shot messages, що й `oneShotDoc`/
|
|
42
|
+
`generateDoc`, але БЕЗ виклику LLM (виклик робить batch-шар одним `submit` на
|
|
43
|
+
всі файли разом). Кидає ту саму помилку pre-send guard, що й `generateDoc`
|
|
44
|
+
(класифікується `permanent` у batch-оркестраторі — skip, не помилка прогону).
|
|
45
|
+
- finishBatchItem — T8 (2b-batch): постобробка ОДНОГО результату `submitBatch` — той самий фініш,
|
|
46
|
+
що й `oneShotDoc`/`finishUnsupported`/det-скорер, тільки без LLM-виклику
|
|
47
|
+
(текст уже отримано з batch-у). Judge-гейт (Stage 3) у batch-шляху НЕ
|
|
48
|
+
викликається (мінімальний обсяг T8 — генерація; judge лишається опційним
|
|
49
|
+
розширенням послідовного шляху).
|
|
39
50
|
|
|
40
51
|
## Гарантії поведінки
|
|
41
52
|
|
|
@@ -602,6 +602,39 @@ const DEFAULT_CONTEXT_TOKENS = 131072
|
|
|
602
602
|
function srcTokenBudget() {
|
|
603
603
|
return Math.floor((Number(env.N_CURSOR_DOCGEN_CTX) || DEFAULT_CONTEXT_TOKENS) * 0.5)
|
|
604
604
|
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Спільний pre-LLM preflight для `generateDoc` і `prepareBatchItem` (T8 2b-batch):
|
|
608
|
+
* читає джерело, ріже гігантів до LLM-виклику (pre-send guard — «Prompt too long»
|
|
609
|
+
* без жодного виклику) і резолвить факт-лист через мовний екстрактор lang-плагіна
|
|
610
|
+
* (js/mjs/ts — lang-js, `.rs` — lang-rust; whole-file `unsupported`-fallback, якщо
|
|
611
|
+
* екстрактора для розширення нема).
|
|
612
|
+
* @param {string} file абсолютний шлях джерела
|
|
613
|
+
* @returns {Promise<{ src: string, estTokens: number, ext: string, langExtractors: Map<string, object>, facts: object }>} усе потрібне обом callers перед LLM-викликом
|
|
614
|
+
*/
|
|
615
|
+
async function loadSrcAndFacts(file) {
|
|
616
|
+
const src = readFileSync(file, 'utf8')
|
|
617
|
+
const estTokens = Math.round(Buffer.byteLength(src, 'utf8') / 4)
|
|
618
|
+
const budget = srcTokenBudget()
|
|
619
|
+
if (estTokens > budget) {
|
|
620
|
+
throw new Error(
|
|
621
|
+
`docgen pre-send guard: джерело ~${estTokens} токенів > бюджет ${budget} (0.5× контексту) — Prompt too long, skip`
|
|
622
|
+
)
|
|
623
|
+
}
|
|
624
|
+
const langExtractors = await loadDocFilesExtractors(process.cwd())
|
|
625
|
+
const ext = `.${file.split('.').pop()}`.toLowerCase()
|
|
626
|
+
const facts = langExtractors.get(ext)?.extractFacts?.(src, file) ?? {
|
|
627
|
+
relPath: file,
|
|
628
|
+
lang: ext.slice(1),
|
|
629
|
+
unsupported: true,
|
|
630
|
+
header: '',
|
|
631
|
+
exports: [],
|
|
632
|
+
imports: {},
|
|
633
|
+
markers: {}
|
|
634
|
+
}
|
|
635
|
+
return { src, estTokens, ext, langExtractors, facts }
|
|
636
|
+
}
|
|
637
|
+
|
|
605
638
|
/**
|
|
606
639
|
* Дефолтна модель: N_CURSOR_DOCGEN_MODEL → resolveModel('min') (→ N_LOCAL_MIN_MODEL).
|
|
607
640
|
* Без хардкод-fallback: модель налаштовує кожен локально (`N_LOCAL_MIN_MODEL`); якщо
|
|
@@ -654,32 +687,8 @@ export async function generateDoc(
|
|
|
654
687
|
deadlineAt = null
|
|
655
688
|
} = {}
|
|
656
689
|
) {
|
|
657
|
-
const src = readFileSync(file, 'utf8')
|
|
658
|
-
// Pre-send guard: весь src вшивається у промпт як є (екстракт фактів його НЕ
|
|
659
|
-
// замінює). Для гігантів (vendored/генерат) це переповнює контекст → інстант-skip
|
|
660
|
-
// без LLM-виклику. Маркер «Prompt too long» → classifyOmlxError → permanent → skip.
|
|
661
690
|
// Guard ДО створення ланцюжка: skip без LLM — не задача.
|
|
662
|
-
const estTokens
|
|
663
|
-
const budget = srcTokenBudget()
|
|
664
|
-
if (estTokens > budget) {
|
|
665
|
-
throw new Error(
|
|
666
|
-
`docgen pre-send guard: джерело ~${estTokens} токенів > бюджет ${budget} (0.5× контексту) — Prompt too long, skip`
|
|
667
|
-
)
|
|
668
|
-
}
|
|
669
|
-
// Факт-лист — лише від мовного екстрактора lang-плагіна (js/mjs/ts —
|
|
670
|
-
// lang-js, `.rs` — lang-rust); без екстрактора для розширення — whole-file
|
|
671
|
-
// шлях через `unsupported` (у ядрі вбудованих екстракторів немає, фаза 5b).
|
|
672
|
-
const langExtractors = await loadDocFilesExtractors(process.cwd())
|
|
673
|
-
const ext = `.${file.split('.').pop()}`.toLowerCase()
|
|
674
|
-
const facts = langExtractors.get(ext)?.extractFacts?.(src, file) ?? {
|
|
675
|
-
relPath: file,
|
|
676
|
-
lang: ext.slice(1),
|
|
677
|
-
unsupported: true,
|
|
678
|
-
header: '',
|
|
679
|
-
exports: [],
|
|
680
|
-
imports: {},
|
|
681
|
-
markers: {}
|
|
682
|
-
}
|
|
691
|
+
const { src, estTokens, ext, langExtractors, facts } = await loadSrcAndFacts(file)
|
|
683
692
|
const t0 = Date.now()
|
|
684
693
|
llmMeter = { calls: 0, ms: 0 }
|
|
685
694
|
const chain = chainFactory({ kind: 'doc-generate', unit: facts.relPath, cwd: process.cwd() })
|
|
@@ -770,6 +779,51 @@ export async function generateDoc(
|
|
|
770
779
|
}
|
|
771
780
|
}
|
|
772
781
|
|
|
782
|
+
/**
|
|
783
|
+
* T8 (2b-batch, рішення Р): підготовка ОДНОГО item-у для `submitBatch` — та сама
|
|
784
|
+
* pre-send guard і той самий факт-лист/one-shot messages, що й `oneShotDoc`/
|
|
785
|
+
* `generateDoc`, але БЕЗ виклику LLM (виклик робить batch-шар одним `submit` на
|
|
786
|
+
* всі файли разом). Кидає ту саму помилку pre-send guard, що й `generateDoc`
|
|
787
|
+
* (класифікується `permanent` у batch-оркестраторі — skip, не помилка прогону).
|
|
788
|
+
* @param {string} file абсолютний шлях джерела
|
|
789
|
+
* @param {{ existingMd?: string|null }} [opts] наявна дока (для захищеної секції «Призначення»)
|
|
790
|
+
* @returns {Promise<{ facts: object, anchors: object|null, src: string, messages: Array<{role:string,content:string}>, intent: string|null }>} усе потрібне для item-у batch-у й пізнішого фінішу
|
|
791
|
+
*/
|
|
792
|
+
export async function prepareBatchItem(file, { existingMd = null } = {}) {
|
|
793
|
+
const { src, facts } = await loadSrcAndFacts(file)
|
|
794
|
+
const anchors = facts.unsupported ? null : extractAnchors(src)
|
|
795
|
+
const intent = existingMd ? splitProtected(existingMd).body : null
|
|
796
|
+
return { facts, anchors, src, messages: oneShotMessages(facts, src), intent }
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* T8 (2b-batch): постобробка ОДНОГО результату `submitBatch` — той самий фініш,
|
|
801
|
+
* що й `oneShotDoc`/`finishUnsupported`/det-скорер, тільки без LLM-виклику
|
|
802
|
+
* (текст уже отримано з batch-у). Judge-гейт (Stage 3) у batch-шляху НЕ
|
|
803
|
+
* викликається (мінімальний обсяг T8 — генерація; judge лишається опційним
|
|
804
|
+
* розширенням послідовного шляху).
|
|
805
|
+
* @param {string} text сирий текст відповіді моделі для цього item-у
|
|
806
|
+
* @param {{ facts: object, anchors: object|null, src: string, intent: string|null, model: string, threshold?: number }} ctx контекст item-у (з `prepareBatchItem`)
|
|
807
|
+
* @returns {{ md: string, score: number|null, issues: string[], degraded: boolean, model: string }} результат генерації для штампу/запису
|
|
808
|
+
*/
|
|
809
|
+
export function finishBatchItem(text, { facts, anchors, src, intent, model, threshold = QUALITY_THRESHOLD }) {
|
|
810
|
+
let md = stripSignatures(stripSection(text))
|
|
811
|
+
if (!md.startsWith('#')) md = `# ${basename(facts.relPath)}\n\n${md}`
|
|
812
|
+
md = insertProtected(md + '\n', intent)
|
|
813
|
+
if (facts.unsupported) {
|
|
814
|
+
const refusal = detectRefusalFiller(splitProtected(md).without)
|
|
815
|
+
return {
|
|
816
|
+
md,
|
|
817
|
+
score: refusal ? 0 : null,
|
|
818
|
+
issues: refusal ? ['refusal-filler'] : [],
|
|
819
|
+
degraded: Boolean(refusal),
|
|
820
|
+
model
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const { score, issues } = scoreDoc(md, facts, { anchors, src })
|
|
824
|
+
return { md, score, issues, degraded: score < threshold, model }
|
|
825
|
+
}
|
|
826
|
+
|
|
773
827
|
// CLI: node docgen-gen.mjs <file> [--model <m>]
|
|
774
828
|
if (isRunAsCli(import.meta.url)) {
|
|
775
829
|
const args = process.argv.slice(2)
|
|
@@ -75,6 +75,8 @@ export async function fixWorker(violations, ctx, deps = {}) {
|
|
|
75
75
|
|
|
76
76
|
/** @type {string[]} */
|
|
77
77
|
const touchedFiles = []
|
|
78
|
+
/** @type {Array<{provider: string, hook: string, files: string[], error: string}>} */
|
|
79
|
+
const failed = []
|
|
78
80
|
/**
|
|
79
81
|
* Викликає опційний fix-hook провайдера, збирає touchedFiles; виняток хука не
|
|
80
82
|
* валить решту хуків/провайдерів — success визначає canonical re-detect.
|
|
@@ -88,6 +90,12 @@ export async function fixWorker(violations, ctx, deps = {}) {
|
|
|
88
90
|
try {
|
|
89
91
|
const res = await provider[hook]({ ...args, cwd: ctx.cwd, ctx: hookCtx(ctx, deadlineAt) })
|
|
90
92
|
touchedFiles.push(...(res?.touchedFiles ?? []))
|
|
93
|
+
for (const failure of res?.failed ?? []) {
|
|
94
|
+
const error = failure?.error ?? 'невідома причина'
|
|
95
|
+
const files = failure?.files ?? []
|
|
96
|
+
failed.push({ provider: provider.id, hook, files, error })
|
|
97
|
+
console.warn(`⚠ coverage fix-worker: ${provider.id}.${hook} failed/no-op: ${error}`)
|
|
98
|
+
}
|
|
91
99
|
} catch (error) {
|
|
92
100
|
console.warn(
|
|
93
101
|
`⚠ coverage fix-worker: ${provider.id}.${hook} впав: ${String(error?.message ?? error).slice(0, 200)}`
|
|
@@ -107,5 +115,5 @@ export async function fixWorker(violations, ctx, deps = {}) {
|
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
return { touchedFiles }
|
|
118
|
+
return { touchedFiles, failed }
|
|
111
119
|
}
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* - glue: CLI entry / runStandardRule wrapper (integration covers)
|
|
10
10
|
* - wrapper: тонкий spawn/fetch wrapper (integration covers)
|
|
11
11
|
*/
|
|
12
|
-
import
|
|
12
|
+
// Namespace import сумісний із Bun+Vitest трансформацією Zod 4, де named `z`
|
|
13
|
+
// може бути undefined попри наявність у runtime ESM namespace.
|
|
14
|
+
import * as z from 'zod'
|
|
13
15
|
|
|
14
16
|
// Трохи ширше за prompt-ліміт (500) — запас на моделі, які трохи перевищують
|
|
15
17
|
// інструкцію; понад це вже truncate-имо самі перед валідацією (REASON_SOFT_MAX нижче).
|
|
@@ -3,34 +3,28 @@ type: JS Module
|
|
|
3
3
|
title: skills-cli.mjs
|
|
4
4
|
resource: npm/scripts/skills-cli.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model:
|
|
6
|
+
crc: 9b3e0ad4
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
9
|
+
score: 25
|
|
10
|
+
issues: no-overview,short-behavior,internal-name:runTazeOrchestratorCli,anchor-miss:tsconfig.json,anchor-miss:.n-rules.json,anchor-miss:.n-cursor.json,anchor-miss:main.json,best-of-2:retry-lost
|
|
8
11
|
---
|
|
9
12
|
|
|
10
|
-
## Огляд
|
|
11
|
-
|
|
12
|
-
Цей модуль забезпечує керування функціоналом скілів пакета `@7n/rules`. Він каталогізує доступні скіли на основі наявності файлу `SKILL.md` у відповідному пакеті. Для виконання скілу збирається контекст, що включає інструкції скілу та інформацію з конфігураційних файлів проєкту: `package.json`, `tsconfig.json`, `.n-rules.json` та `main.json`. Система дозволяє або вивести список доступних скілів (`npx @7n/rules skill list`), або ініціювати виконання обраного скілу (наприклад, `npx @7n/rules skill pi taze`), з можливістю передачі додаткових аргументів. Пріоритетним механізмом виконання є інтеграція з вбудованим pi-агентом; поряд з ним підтримуються повноцінні зовнішні ACP-агенти (`cursor`, `codex`) і deprecated `claude`.
|
|
13
|
-
|
|
14
|
-
## Поведінка
|
|
15
|
-
|
|
16
|
-
Поведінка
|
|
17
|
-
normalizeSkillId знімає префікс `n-` з імені скілу для приведення його до стандартного ID.
|
|
18
|
-
listSkillIds отримує відсортований список ID скілів, які мають файл `SKILL.md` у вказаній директорії скілів.
|
|
19
|
-
buildSkillPrompt збирає комплексний промпт для виконання скілу, об'єднуючи інструкцію скілу з конфігураційними файлами проєкту, такими як `package.json`, `tsconfig.json` та `.n-rules.json`.
|
|
20
|
-
resolveBundledPackageRoot визначає абсолютний шлях до кореня пакету `@7n/rules`, використовуючи інформацію про поточний модуль.
|
|
21
|
-
runSkillsCli виконує логіку командного інтерфейсу для керування скілами: може вивести список доступних скілів, зібрати промпт для скілу або ініціювати його виконання через вбудований pi-агент чи один із зовнішніх CLI-раннерів (`cursor`, `codex`, `claude`).
|
|
22
|
-
|
|
23
13
|
## Публічний API
|
|
24
14
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
15
|
+
- resolveBundledPackageRoot — Корінь пакета `@7n/rules` (каталог з `skills/`, `rules/`, …).
|
|
16
|
+
- isTazeOrchestratorSkillArgs — Чи `argv` (аргументи після `skill`) резолвиться в JS-оркестрований
|
|
17
|
+
worktree-only `taze`-шлях (`runTazeOrchestratorCli`) — той самий критерій,
|
|
18
|
+
що й нижче в `runSkillsCli`. Використовується `n-rules.js`, щоб не мутувати
|
|
19
|
+
root `package.json` (self-upgrade `@7n/rules`) ДО власного worktree-гейту
|
|
20
|
+
оркестратора: той сам створює worktree і перевіряє чистоту дерева
|
|
21
|
+
(`ensureRunningInWorktree`, `requireCleanTree: true`) — мутація package.json
|
|
22
|
+
прямо перед цим викликом примусово провалила б auto-create там, де дерево
|
|
23
|
+
інакше було б чисте.
|
|
24
|
+
- isJsOrchestratedSkillArgs — Чи аргументи ведуть у будь-який JS-оркестрований skill. Потрібно верхньому
|
|
25
|
+
CLI, щоб не мутувати root package.json self-upgrade-ом до власного preflight
|
|
26
|
+
оркестратора.
|
|
30
27
|
|
|
31
28
|
## Гарантії поведінки
|
|
32
29
|
|
|
33
|
-
-
|
|
34
|
-
- Тира моделі для `pi`-runner береться з `main.json.tier` скіла (дефолт `max`).
|
|
35
|
-
- ACP-раннер автоапрувляє `session/request_permission` (без інтерактивних питань) — паритет із колишнім non-interactive режимом; `Client`-реалізація читає/пише файли напряму через `node:fs`.
|
|
36
|
-
- Лише `claude` — deprecated: друкує попередження й лишається як fallback, доки не налаштовано pi-модель. `cursor`/`codex` — повноцінні раннери без попередження.
|
|
30
|
+
- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* agentclientprotocol.com) — JSON-RPC поверх stdio.
|
|
4
4
|
*
|
|
5
5
|
* `cursor`/`codex` мігрували на `@7n/llm-lib/acp` (napi-міст до
|
|
6
|
-
* `
|
|
6
|
+
* `llm_lib::acp` — та сама протокольна логіка, але в Rust, без
|
|
7
7
|
* дублювання в JS). `claude` лишається тут як окремий JS-шим, бо Rust-крейт
|
|
8
8
|
* `AcpAgentKind` його не моделює (лише `Cursor`/`Codex`); підʼєднання йде
|
|
9
9
|
* через офіційний TS SDK `@zed-industries/agent-client-protocol`, а дозволи
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Semantic-collateral veto для verdict-фази fix-pipeline
|
|
3
|
-
* (spec docs/specs/2026-06-26-pi-fix-engine-migration.md §12, addendum 2026-07-05
|
|
3
|
+
* (spec docs/specs/2026-06-26-pi-fix-engine-migration.md §12, addendum 2026-07-05;
|
|
4
|
+
* in-file hunk-level розширення — addendum 2026-07-24 після колатеральної правки, що
|
|
5
|
+
* видалила задокументований обхід реального Bun SQL бага в upsert-order.js).
|
|
4
6
|
*
|
|
5
7
|
* Клас collateral слабких локальних моделей: «виправляючи» правило, модель робить
|
|
6
8
|
* семантичну правку у сторонньому файлі, яка НЕ порушує жодного правила й тому
|
|
@@ -12,6 +14,15 @@
|
|
|
12
14
|
* їх однаково покриває re-check зачеплених файлів і rollback. Порожній target-set
|
|
13
15
|
* (whole-repo концерни без `file` у violations) → veto незастосовний (fail-open,
|
|
14
16
|
* повертає []) — свідомо, щоб не ламати концерни без file-атрибуції.
|
|
17
|
+
*
|
|
18
|
+
* Другий клас collateral (§12 addendum 2026-07-24): rung змінює файл, що ВЖЕ входить
|
|
19
|
+
* у target-set (легітимна ціль порушення), але зачіпає рядки поза ділянкою самого
|
|
20
|
+
* порушення — напр. LLM виправляє відсутній JSDoc над функцією і заодно видаляє сусідній
|
|
21
|
+
* задокументований обхід реального бага усередині тіла тієї самої функції. Cross-file
|
|
22
|
+
* veto вище цього не бачить (файл — законна ціль). {@link findInFileCollateralEdits}
|
|
23
|
+
* рахує грубий (common-prefix/common-suffix, не повний Myers-diff) змінений рядковий
|
|
24
|
+
* діапазон і звіряє його з вікном навколо кожної `violation.data.line` того ж файлу;
|
|
25
|
+
* fail-open, якщо рядок порушення невідомий (немає як відповідально обмежити hunk).
|
|
15
26
|
*/
|
|
16
27
|
import { realpathSync } from 'node:fs'
|
|
17
28
|
import { basename, dirname, isAbsolute, join, resolve } from 'node:path'
|
|
@@ -37,6 +48,18 @@ export function realpathBestEffort(p) {
|
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Нормалізує target-set порушення у множину realpath-абсолютних шляхів — спільна
|
|
53
|
+
* основа і для collateral-veto (файли ПОЗА target-set), і для test-gate
|
|
54
|
+
* (файли ВСЕРЕДИНІ target-set, для яких перевіряються сестринські тести).
|
|
55
|
+
* @param {string[]} targetFiles Файли порушення, відносні до cwd або абсолютні.
|
|
56
|
+
* @param {string} cwd Робоча директорія для резолву відносних шляхів.
|
|
57
|
+
* @returns {Set<string>} Нормалізовані абсолютні шляхи target-set (може бути порожньою).
|
|
58
|
+
*/
|
|
59
|
+
export function resolveTargetSet(targetFiles, cwd) {
|
|
60
|
+
return new Set(targetFiles.map(f => realpathBestEffort(isAbsolute(f) ? f : resolve(cwd, f))))
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
/**
|
|
41
64
|
* Обчислює collateral-правки rung-а: наявні (на момент S1) файли, змінені поза
|
|
42
65
|
* target-set порушення. Runner на непорожньому результаті відхиляє clean-вердикт
|
|
@@ -49,7 +72,60 @@ export function realpathBestEffort(p) {
|
|
|
49
72
|
* veto не спрацював (нема collateral або target-set невідомий)
|
|
50
73
|
*/
|
|
51
74
|
export function findCollateralEdits({ modifiedExisting, targetFiles, cwd }) {
|
|
52
|
-
const targets =
|
|
75
|
+
const targets = resolveTargetSet(targetFiles, cwd)
|
|
53
76
|
if (targets.size === 0) return []
|
|
54
77
|
return modifiedExisting.map(p => realpathBestEffort(p)).filter(abs => !targets.has(abs))
|
|
55
78
|
}
|
|
79
|
+
|
|
80
|
+
/** Дефолтне вікно (рядків з обох боків `violation.data.line`), у межах якого зміна вважається «поруч із порушенням». */
|
|
81
|
+
export const HUNK_WINDOW = 20
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Обчислює грубий змінений рядковий діапазон (1-indexed, у координатах `current`) між
|
|
85
|
+
* pre-image і поточним вмістом файлу: найдовший спільний префікс + найдовший спільний
|
|
86
|
+
* суфікс рядків, все між ними — «змінене». Це НЕ повний diff (не бачить кількох
|
|
87
|
+
* розрізнених hunk-ів як окремих інтервалів — лише зовнішні межі першої і останньої
|
|
88
|
+
* розбіжності), але достатньо, щоб відрізнити «зміна лишилась у ділянці порушення» від
|
|
89
|
+
* «зачепило щось далеко» — для повного Myers-diff тут немає залежності, а точність
|
|
90
|
+
* hunk-рівня явно позначена як бажаний, але не обов'язковий бар (spec §12 addendum
|
|
91
|
+
* 2026-07-24).
|
|
92
|
+
* @param {string} preImage Вміст файлу на момент S1.
|
|
93
|
+
* @param {string} current Поточний вміст файлу.
|
|
94
|
+
* @returns {{ start: number, end: number }|null} діапазон зміни, або null якщо вміст ідентичний.
|
|
95
|
+
*/
|
|
96
|
+
function changedLineRange(preImage, current) {
|
|
97
|
+
if (preImage === current) return null
|
|
98
|
+
const preLines = preImage.split('\n')
|
|
99
|
+
const curLines = current.split('\n')
|
|
100
|
+
const maxLcp = Math.min(preLines.length, curLines.length)
|
|
101
|
+
let lcp = 0
|
|
102
|
+
while (lcp < maxLcp && preLines[lcp] === curLines[lcp]) lcp++
|
|
103
|
+
const maxLcs = maxLcp - lcp
|
|
104
|
+
let lcs = 0
|
|
105
|
+
while (lcs < maxLcs && preLines[preLines.length - 1 - lcs] === curLines[curLines.length - 1 - lcs]) lcs++
|
|
106
|
+
const start = lcp + 1
|
|
107
|
+
const end = curLines.length - lcs
|
|
108
|
+
return start > end ? null : { start, end }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* In-file hunk-level veto (§12 addendum 2026-07-24): rung змінив файл, що вже входить
|
|
113
|
+
* у target-set порушення, але змінений рядковий діапазон виходить за межі вікна навколо
|
|
114
|
+
* КОЖНОЇ `violation.data.line` цього файлу — сигнал колатеральної правки поза власне
|
|
115
|
+
* порушенням (клас upsert-order.js: LLM видалив сусідній задокументований обхід бага,
|
|
116
|
+
* виправляючи doc-comment над функцією). Fail-open: якщо для файлу немає жодного
|
|
117
|
+
* порушення з відомим `line`, hunk неможливо відповідально обмежити — повертає null.
|
|
118
|
+
* @param {{ preImage: string|null, current: string|null, violationLines: number[], window?: number }} args
|
|
119
|
+
* preImage/current — вміст файлу до/після rung-а; violationLines — номери рядків
|
|
120
|
+
* порушень ЦЬОГО rung-а в ЦЬОМУ файлі; window — половина ширини допустимого вікна.
|
|
121
|
+
* @returns {{ start: number, end: number }|null} діапазон відхиленої правки, або null —
|
|
122
|
+
* veto не спрацював (нема зміни, зміна в межах вікна, або невідома локація порушення).
|
|
123
|
+
*/
|
|
124
|
+
export function findInFileCollateralEdits({ preImage, current, violationLines, window = HUNK_WINDOW }) {
|
|
125
|
+
if (preImage === null || preImage === undefined || current === null || current === undefined) return null
|
|
126
|
+
if (!violationLines || violationLines.length === 0) return null
|
|
127
|
+
const range = changedLineRange(preImage, current)
|
|
128
|
+
if (!range) return null
|
|
129
|
+
const inWindow = violationLines.some(line => range.start >= line - window && range.end <= line + window)
|
|
130
|
+
return inWindow ? null : range
|
|
131
|
+
}
|
|
@@ -3,26 +3,40 @@ type: JS Module
|
|
|
3
3
|
title: collateral-veto.mjs
|
|
4
4
|
resource: npm/scripts/lib/lint-surface/collateral-veto.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 96d4ee38
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
9
|
+
score: 55
|
|
10
|
+
issues: no-overview,short-behavior,best-of-2:retry-lost
|
|
7
11
|
---
|
|
8
12
|
|
|
9
|
-
## Огляд
|
|
10
|
-
|
|
11
|
-
Semantic-collateral veto для verdict-фази fix-pipeline (spec pi-fix-engine-migration §12, addendum 2026-07-05). Закриває клас collateral слабких локальних моделей: «виправляючи» правило, модель робить семантичну правку у сторонньому файлі, яка не порушує жодного правила й тому проходить canonical re-detect (кейс App.vue: хардкод версії з коментарем «we simulate it being available» замість виклику `getVersion`).
|
|
12
|
-
|
|
13
|
-
## Поведінка
|
|
14
|
-
|
|
15
|
-
1. `findCollateralEdits` порівнює список змінених наявних файлів rung-а (`snapshot.modifiedExisting()`) із target-set порушення (`violations[].file ∪ item.files`) і повертає правки поза target-set.
|
|
16
|
-
2. Нові файли до veto не входять — легітимний клас (scaffold, доки поряд із кодом); їх покриває re-check зачеплених файлів і rollback.
|
|
17
|
-
3. Порожній target-set (whole-repo концерни без `file` у violations) → veto незастосовний: свідомий fail-open, повертається порожній масив.
|
|
18
|
-
4. Усі шляхи realpath-нормалізуються (`realpathBestEffort`, той самий патерн, що у write-guard llm-lib) — знімає symlink-розбіжності macOS (`/tmp` → `/private/tmp`); caller relativize-ить результати від так само нормалізованого cwd.
|
|
19
|
-
|
|
20
13
|
## Публічний API
|
|
21
14
|
|
|
22
|
-
-
|
|
23
|
-
|
|
15
|
+
- realpathBestEffort — realpath шляху з найкращих зусиль: для наявного — повний realpath; для ще-неіснуючого —
|
|
16
|
+
realpath батьківської теки + basename; інакше — як є. Знімає розбіжність symlink-шляхів
|
|
17
|
+
(macOS `/tmp` → `/private/tmp`) між snapshot-ключами і target-set (той самий патерн,
|
|
18
|
+
що у write-guard llm-lib). Експортовано, щоб caller relativize-ив результати veto від
|
|
19
|
+
так само нормалізованого cwd.
|
|
20
|
+
- resolveTargetSet — Нормалізує target-set порушення у множину realpath-абсолютних шляхів — спільна
|
|
21
|
+
основа і для collateral-veto (файли ПОЗА target-set), і для test-gate
|
|
22
|
+
(файли ВСЕРЕДИНІ target-set, для яких перевіряються сестринські тести).
|
|
23
|
+
- findCollateralEdits — Обчислює collateral-правки rung-а: наявні (на момент S1) файли, змінені поза
|
|
24
|
+
target-set порушення. Runner на непорожньому результаті відхиляє clean-вердикт
|
|
25
|
+
rung-а (rollback + feedback + телеметрія `kind:"collateral-veto"`).
|
|
26
|
+
modifiedExisting — абсолютні шляхи наявних файлів, змінених відносно S1
|
|
27
|
+
(`snapshot.modifiedExisting()`); targetFiles — файли порушення
|
|
28
|
+
(`violations[].file ∪ item.files`), відносні до cwd або абсолютні.
|
|
29
|
+
- HUNK_WINDOW — Дефолтне вікно (рядків з обох боків `violation.data.line`), у межах якого зміна вважається «поруч із порушенням».
|
|
30
|
+
- findInFileCollateralEdits — In-file hunk-level veto (§12 addendum 2026-07-24): rung змінив файл, що вже входить
|
|
31
|
+
у target-set порушення, але змінений рядковий діапазон виходить за межі вікна навколо
|
|
32
|
+
КОЖНОЇ `violation.data.line` цього файлу — сигнал колатеральної правки поза власне
|
|
33
|
+
порушенням (клас upsert-order.js: LLM видалив сусідній задокументований обхід бага,
|
|
34
|
+
виправляючи doc-comment над функцією). Fail-open: якщо для файлу немає жодного
|
|
35
|
+
порушення з відомим `line`, hunk неможливо відповідально обмежити — повертає null.
|
|
36
|
+
preImage/current — вміст файлу до/після rung-а; violationLines — номери рядків
|
|
37
|
+
порушень ЦЬОГО rung-а в ЦЬОМУ файлі; window — половина ширини допустимого вікна.
|
|
24
38
|
|
|
25
39
|
## Гарантії поведінки
|
|
26
40
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
41
|
+
- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
|
|
42
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
@@ -24,6 +24,7 @@ resource: npm/scripts/lib/lint-surface/
|
|
|
24
24
|
| [run-fix.mjs](run-fix.md) | JS Module |
|
|
25
25
|
| [scheduler.mjs](scheduler.md) | JS Module |
|
|
26
26
|
| [snapshot.mjs](snapshot.md) | JS Module |
|
|
27
|
+
| [test-gate.mjs](test-gate.md) | JS Module |
|
|
27
28
|
| [tier-sampling-bench.mjs](tier-sampling-bench.md) | JS Module |
|
|
28
29
|
| [tier-sampling-experiment.mjs](tier-sampling-experiment.md) | JS Module |
|
|
29
30
|
| [types.mjs](types.md) | JS Module |
|
|
@@ -3,33 +3,18 @@ type: JS Module
|
|
|
3
3
|
title: run-fix.mjs
|
|
4
4
|
resource: npm/scripts/lib/lint-surface/run-fix.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model:
|
|
6
|
+
crc: 7f20adb7
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
9
|
+
score: 50
|
|
10
|
+
issues: no-overview,short-behavior,anchor-miss:.mt.json,best-of-2:retry-lost
|
|
8
11
|
---
|
|
9
12
|
|
|
10
|
-
## Огляд
|
|
11
|
-
|
|
12
|
-
Цей файл реалізує уніфіковану поверхню для фіксації порушень лінтування (unified lint surface) відповідно до специфікації `2026-06-29 §Fix Role / §Tier Ladder`. Він керує послідовним процесом виявлення та усунення проблем, який складається з етапів: виявлення → (очищення/збереження) → Т0 (перманентне виправлення) → знімок S1 → повторне виявлення → (очищення/збереження) → цикл `ladder[відновлення S1 → worker → виявлення]*` (до вичерпання) → (вичерпання/відкат S1). Публічна функція `fixConcern` відповідає за індивідуальне виправлення певного компонента, а `runFixPipeline` ініціює весь комплексне виконання пайплайну. Ролі є чітко розподілені: `detector` лише виявляє, тоді як `T0` та `worker` здійснюють зміни. Успішне завершення визначається виключно канонічним повторним виявленням.
|
|
13
|
-
|
|
14
|
-
## Поведінка
|
|
15
|
-
|
|
16
|
-
Поведінка:
|
|
17
|
-
fixConcern застосовує детерміновані патерни (T0), а потім, якщо це можливо, послідовно виконує ланцюжок фікс-операцій (ladder) для виявлення та усунення порушень певного concern-а.
|
|
18
|
-
runFixPipeline керує повним циклом виправлення: він детектирує всі порушення, застосовує виправлення для кожного знайденого concern-а через `fixConcern`, і виводить фінальний звіт про нездоланні порушення.
|
|
19
|
-
Per-tier timeout (ADR 260620-0556): кожен rung передає worker-у свій `timeoutMs` через `FixContext` (шлях до `runAgentFix opts.timeoutMs`, який abort-ить LLM-сесію), а сам виклик worker-а додатково огорнутий backstop-гонкою ×1.25 від `rung.timeoutMs` — worker, що ігнорує таймаут (зокрема зависла cloud-SSE), фейлить rung помилкою `fix timeout …` замість блокувати lint назавжди; така помилка класифікується як quality і ladder ескалює далі.
|
|
20
|
-
Semantic-collateral veto (spec pi-fix-engine-migration §12, addendum 2026-07-05): clean-вердикт rung-а не приймається, якщо rung змінив наявні файли поза target-set порушення (`violations[].file ∪ item.files`, звірка через `collateral-veto.mjs` за `snapshot.modifiedExisting()`); наслідок — rollback S1, `🚫`-лог, feedback наступному rung-у й телеметрія `kind:"collateral-veto"` у глобальний llm-trace. Нові файли дозволені; порожній target-set → veto незастосовний (fail-open).
|
|
21
|
-
Evidence-гейт рунга (Фаза A1 run-harness, спека 2026-07-11): кожен rung отримує у `FixContext` `verify` — item-scoped canonical re-detect (той самий детектор, що й фінальний вердикт), і `verifyMax` per tier (local — 1, cloud — 2); worker прокидає їх у `runAgentFix`, де провал verify інʼєктиться фідбеком у ту саму pi-сесію. Зовнішній canonical re-detect після worker-а лишається єдиним вердиктом рунга; помилка детектора всередині verify ковтається у `{ok:false}` — зовнішній detect кине її штатно.
|
|
22
|
-
Durable-write-и (issue nitra/cursor#16): worker отримує у `FixContext` поруч із `recordWrite` опційний `recordDurableWrite` — для записів, кожен з яких є самодостатнім кінцевим станом (doc-files: дока зі свіжим CRC). Такі файли переживають rollback провального rung-а і не входять у collateral-veto: частковий прогрес великого батчу не стирається, canonical re-detect наступного rung-а/прогону рахує лише те, що реально лишилось.
|
|
23
|
-
MT-tail (Фаза B, спека 2026-07-11): коли лишився невиправлений хвіст (worst=1), `renderRemaining` повертає зібрані порушення, і вони матеріалізуються у вузли MT-графа через `materializeTail` (mt-tail.mjs). Єдиний гейт — onboarded-репо (наявність `.mt.json`); fail-open: MT недоступний або будь-яка помилка → лог, lint не падає.
|
|
24
|
-
Distillation-телеметрія (Фаза C, §13 pi-migration): успішний agentic-рунг (canonical clean, без veto, з реальними правками у worker telemetry) пише запис `oldText→newText` у глобальний стор (`recordFixTelemetry`, `~/.n-rules/telemetry/<rule>/open/`) — корпус для маховика дистиляції T0. Best-effort; T0/ручні фікси не пишуться.
|
|
25
|
-
Rollback на провалі re-detect-а: якщо canonical re-detect усередині rung-а сам кидає виняток (worker/LLM лишив файл синтаксично невалідним — детектор/conftest не може його розпарсити), `runRung` спершу відкочує `snapshot` до S1, і лише потім перекидає виняток далі — без цього зіпсований проміжний стан worker-а лишався б на диску назавжди (виняток абортує весь прогін до звичайного rollback-коду).
|
|
26
|
-
skipLocalTier (concern-meta.mjs): `selectLadder` перед циклом ladder-а фільтрує з нього local-min/local-min-retry rung-и, якщо `item.entry.concern.skipLocalTier === true` — перша спроба одразу йде на cloud-min. Для concern-ів, де local-tier емпірично майже завжди лише витрачає бюджет rung-а без результату (виявлено на реальному прогоні 2026-07-18: 0/12 успіхів local-tier для `js/eslint`).
|
|
27
|
-
|
|
28
13
|
## Публічний API
|
|
29
14
|
|
|
30
|
-
fixConcern —
|
|
31
|
-
runFixPipeline —
|
|
15
|
+
- fixConcern — Проводить ОДИН concern по pipeline: T0 → S1 → ladder. Повертає чи закрито.
|
|
16
|
+
- runFixPipeline — Повний fix-pipeline: detect усе → fix кожен провальний concern → exit code.
|
|
32
17
|
|
|
33
18
|
## Гарантії поведінки
|
|
34
19
|
|
|
35
|
-
-
|
|
20
|
+
- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
|
|
@@ -3,27 +3,16 @@ type: JS Module
|
|
|
3
3
|
title: snapshot.mjs
|
|
4
4
|
resource: npm/scripts/lib/lint-surface/snapshot.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model:
|
|
8
|
-
|
|
6
|
+
crc: 949c1cac
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
9
|
+
score: 55
|
|
10
|
+
issues: no-overview,short-behavior,best-of-2:retry-lost
|
|
9
11
|
---
|
|
10
12
|
|
|
11
|
-
## Огляд
|
|
12
|
-
|
|
13
|
-
Централізований знімок попереднього образу (pre-image snapshot) та механізм відкату (rollback) забезпечують функціональність фікс-пайплайну відповідно до специфікації spec 2026-06-29, §Tier Ladder. Відкат відновлює попередній стан файлів, але не видаляє ті, що відсутні у знімку. Операції виконуються послідовно для кожного окремого concern-а, гарантуючи, що стан кожного concern-а базується на успішних змінах попередніх concern-ів. До запису в tracker.record ПЕРЕД мутацією забезпечується фіксація попереднього образу для кожного потенційно зміненого файлу. Окремо підтримуються durable-write-и — самодостатні кінцеві стани (напр. файлові доки зі свіжим CRC), які rollback не чіпає.
|
|
14
|
-
|
|
15
|
-
## Поведінка
|
|
16
|
-
|
|
17
|
-
1. Створюється об'єкт-трекер попереднього стану. Цей трекер фіксує вихідний стан файлів, які можуть бути змінені в процесі виправлення.
|
|
18
|
-
2. Для фіксації стану файлу використовується механізм `record`, який зберігає вміст файлу або маркер відсутності, якщо файл на момент фіксації не існував.
|
|
19
|
-
3. Механізм `recordDurable` позначає шлях як durable: запис у такий файл — самодостатній кінцевий стан, тож rollback його не відновлює й не видаляє, а semantic-collateral veto його не враховує. Це дозволяє batch-worker-ам (doc-files) зберігати частковий прогрес після провалу/таймауту rung-а — беклог сходиться за кілька прогонів (issue nitra/cursor#16).
|
|
20
|
-
4. Після виконання змін, трекеру викликається `rollback`. Цей метод відновлює вміст усіх зафіксованих файлів (крім durable-позначених), а файли, які відсутні на момент фіксації, видаляє.
|
|
21
|
-
5. Щоб отримати список усіх файлів, чий попередній стан був зафіксований або які позначені durable, використовується метод `touched`.
|
|
22
|
-
6. Метод `modifiedExisting` повертає наявні на момент S1 файли, чий поточний вміст відрізняється від pre-image (видалення наявного файлу теж вважається зміною) — вхід semantic-collateral veto; нові файли та durable-шляхи до результату не входять.
|
|
23
|
-
|
|
24
13
|
## Публічний API
|
|
25
14
|
|
|
26
|
-
- createSnapshot —
|
|
15
|
+
- createSnapshot — Створює tracker зі свіжим (порожнім) набором pre-images — це і є snapshot S1.
|
|
27
16
|
|
|
28
17
|
## Гарантії поведінки
|
|
29
18
|
|