@nitra/cursor 3.21.1 → 3.23.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/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
- package/CHANGELOG.md +37 -3
- package/bin/docs/n-cursor.md +636 -0
- package/bin/docs/rename-yaml-extensions.md +207 -0
- package/bin/n-cursor.js +30 -3
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +18 -0
- package/rules/abie/js/docs/applies.md +26 -0
- package/rules/abie/js/docs/env_dns.md +32 -0
- package/rules/abie/js/docs/firebase_hosting.md +23 -0
- package/rules/abie/js/docs/hc_pairing.md +35 -0
- package/rules/abie/js/docs/ua_http_route.md +28 -0
- package/rules/abie/js/docs/ua_node_selector.md +28 -0
- package/rules/abie/lib/docs/enabled.md +29 -0
- package/rules/abie/lib/docs/env-dns.md +35 -0
- package/rules/abie/lib/docs/hc-yaml.md +33 -0
- package/rules/abie/lib/docs/http-route.md +44 -0
- package/rules/abie/lib/docs/k8s-tree.md +40 -0
- package/rules/abie/lib/docs/kustomization-patches.md +47 -0
- package/rules/abie/lib/docs/overlay-paths.md +38 -0
- package/rules/abie/lib/docs/yaml.md +29 -0
- package/rules/adr/docs/fix.md +148 -0
- package/rules/adr/js/docs/hooks.md +259 -0
- package/rules/bun/docs/fix.md +156 -0
- package/rules/bun/js/docs/layout.md +393 -0
- package/rules/capacitor/docs/fix.md +121 -0
- package/rules/capacitor/js/docs/platforms.md +295 -0
- package/rules/changelog/changelog.mdc +2 -2
- package/rules/changelog/docs/fix.md +174 -0
- package/rules/changelog/js/consistency.mjs +114 -13
- package/rules/changelog/js/docs/consistency.md +387 -0
- package/rules/changelog/lib/docs/package-manifest.md +210 -0
- package/rules/ci4/docs/fix.md +179 -0
- package/rules/ci4/js/docs/marksman_config.md +128 -0
- package/rules/docker/docker.mdc +8 -3
- package/rules/docker/docs/fix.md +171 -0
- package/rules/docker/js/docs/lint.md +258 -0
- package/rules/docker/lib/docs/docker-hadolint.md +184 -0
- package/rules/docker/lib/docs/docker-mirror.md +247 -0
- package/rules/docker/lib/docs/docker-native-addon.md +170 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +219 -0
- package/rules/docker/lint/docs/lint.md +193 -0
- package/rules/efes/docs/fix.md +203 -0
- package/rules/feedback/docs/fix.md +140 -0
- package/rules/flow/docs/fix.md +152 -0
- package/rules/ga/docs/fix.md +158 -0
- package/rules/ga/js/docs/lint.md +100 -0
- package/rules/ga/js/docs/workflows.md +217 -0
- package/rules/ga/lint/docs/lint.md +209 -0
- package/rules/ga/policy/clean_merged_branch/clean_merged_branch.rego +11 -2
- package/rules/ga/policy/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +3 -4
- package/rules/graphql/docs/fix.md +126 -0
- package/rules/graphql/js/docs/tooling.md +264 -0
- package/rules/graphql/lib/docs/graphql-gql-scan.md +219 -0
- package/rules/hasura/docs/fix.md +120 -0
- package/rules/hasura/hasura.mdc +14 -0
- package/rules/hasura/js/docs/internal_urls.md +326 -0
- package/rules/image-avif/docs/fix.md +132 -0
- package/rules/image-avif/js/docs/avif_generation.md +241 -0
- package/rules/image-compress/docs/fix.md +150 -0
- package/rules/image-compress/js/docs/package_setup.md +191 -0
- package/rules/js-bun-db/docs/fix.md +148 -0
- package/rules/js-bun-db/js/docs/safety.md +231 -0
- package/rules/js-bun-db/js-bun-db.mdc +42 -13
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +347 -0
- package/rules/js-bun-redis/docs/fix.md +123 -0
- package/rules/js-bun-redis/js/docs/imports.md +176 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +223 -0
- package/rules/js-lint/docs/fix.md +117 -0
- package/rules/js-lint/js/docs/lint.md +250 -0
- package/rules/js-lint/js/docs/tooling.md +348 -0
- package/rules/js-lint/js/docs/utils_imports.md +207 -0
- package/rules/js-lint/js/lint-findings.mjs +110 -0
- package/rules/js-lint/js/lint.mjs +86 -15
- package/rules/js-lint-ci/docs/fix.md +154 -0
- package/rules/js-lint-ci/js/docs/lint.md +144 -0
- package/rules/js-mssql/docs/fix.md +128 -0
- package/rules/js-mssql/js/docs/deps.md +263 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +367 -0
- package/rules/js-run/docs/fix.md +144 -0
- package/rules/js-run/js/docs/runtime.md +388 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +117 -0
- package/rules/js-run/lib/docs/check-env-scan.md +433 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +300 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +204 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +326 -0
- package/rules/k8s/docs/fix.md +129 -0
- package/rules/k8s/js/docs/manifests.md +344 -0
- package/rules/k8s/js/manifests.mjs +6 -2
- package/rules/k8s/k8s.mdc +4 -2
- package/rules/k8s/lint/docs/lint.md +411 -0
- package/rules/k8s/policy/network_policy/template/deployment.snippet.yaml +2 -0
- package/rules/k8s/policy/network_policy/template/stateful-set.snippet.yaml +2 -0
- package/rules/nginx-default-tpl/docs/fix.md +124 -0
- package/rules/nginx-default-tpl/js/docs/template.md +378 -0
- package/rules/npm-module/docs/fix.md +98 -0
- package/rules/npm-module/js/docs/package_structure.md +274 -0
- package/rules/npm-module/js/docs/rule_meta.md +137 -0
- package/rules/npm-module/js/docs/skill_meta.md +190 -0
- package/rules/php/docs/fix.md +107 -0
- package/rules/php/js/docs/tooling.md +152 -0
- package/rules/php/lint/docs/lint.md +215 -0
- package/rules/python/docs/fix.md +163 -0
- package/rules/python/js/docs/applies.md +108 -0
- package/rules/python/js/docs/tooling.md +153 -0
- package/rules/python/lint/docs/lint.md +322 -0
- package/rules/rego/docs/fix.md +121 -0
- package/rules/rego/js/docs/applies.md +174 -0
- package/rules/rego/js/docs/lint.md +118 -0
- package/rules/rego/lint/docs/lint.md +204 -0
- package/rules/release/docs/change.md +185 -0
- package/rules/release/docs/fix.md +119 -0
- package/rules/release/docs/release.md +222 -0
- package/rules/release/lib/docs/aggregate.md +246 -0
- package/rules/release/lib/docs/change-file.md +200 -0
- package/rules/release/lib/docs/fallback.md +203 -0
- package/rules/rust/docs/fix.md +129 -0
- package/rules/rust/js/docs/applies.md +140 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +130 -0
- package/rules/security/docs/fix.md +86 -0
- package/rules/security/js/docs/lint.md +171 -0
- package/rules/security/js/docs/sample_secret.md +190 -0
- package/rules/security/js/docs/trufflehog.md +137 -0
- package/rules/security/js/lint.mjs +9 -1
- package/rules/style-lint/docs/fix.md +155 -0
- package/rules/style-lint/js/docs/lint.md +184 -0
- package/rules/style-lint/js/docs/tooling.md +194 -0
- package/rules/tauri/docs/fix.md +158 -0
- package/rules/tauri/js/docs/cargo_mutants_config.md +168 -0
- package/rules/tauri/js/docs/tooling.md +228 -0
- package/rules/test/coverage/coverage.mjs +15 -3
- package/rules/test/docs/fix.md +132 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +138 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +134 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +160 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +195 -0
- package/rules/test/js/docs/cargo_mutants_config.md +173 -0
- package/rules/test/js/docs/location.md +136 -0
- package/rules/test/js/docs/no-process-chdir.md +160 -0
- package/rules/test/js/docs/no-relative-fs-path.md +271 -0
- package/rules/test/js/docs/stryker_config.md +152 -0
- package/rules/test/js/docs/vitest-config-pool-forks.md +174 -0
- package/rules/text/docs/fix.md +118 -0
- package/rules/text/js/docs/forbidden-prettier.md +143 -0
- package/rules/text/js/docs/formatting.md +256 -0
- package/rules/text/js/docs/lint.md +122 -0
- package/rules/text/lint/docs/lint.md +220 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +157 -0
- package/rules/text/lint/docs/run-shellcheck.md +212 -0
- package/rules/text/lint/docs/run-v8r.md +197 -0
- package/rules/vue/docs/fix.md +127 -0
- package/rules/vue/js/docs/packages.md +335 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +261 -0
- package/rules/worktree/docs/fix.md +161 -0
- package/schemas/rule-meta.json +5 -1
- package/scripts/auto-rules.mjs +7 -4
- package/scripts/coverage-classify/docs/apply.md +202 -0
- package/scripts/coverage-classify/docs/cache.md +203 -0
- package/scripts/coverage-classify/docs/index.md +218 -0
- package/scripts/coverage-classify/docs/prompt.md +132 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +169 -0
- package/scripts/coverage-fix-extract.mjs +122 -0
- package/scripts/coverage-fix.mjs +1 -1
- package/scripts/dispatcher/docs/graph.md +346 -0
- package/scripts/dispatcher/docs/index.md +236 -0
- package/scripts/dispatcher/docs/trace.md +296 -0
- package/scripts/dispatcher/index.mjs +1 -1
- package/scripts/dispatcher/lib/active.mjs +4 -8
- package/scripts/dispatcher/lib/commands.mjs +7 -11
- package/scripts/dispatcher/lib/docs/active.md +348 -0
- package/scripts/dispatcher/lib/docs/artifact.md +232 -0
- package/scripts/dispatcher/lib/docs/budget.md +167 -0
- package/scripts/dispatcher/lib/docs/capability.md +196 -0
- package/scripts/dispatcher/lib/docs/commands.md +210 -0
- package/scripts/dispatcher/lib/docs/events.md +182 -0
- package/scripts/dispatcher/lib/docs/executor.md +190 -0
- package/scripts/dispatcher/lib/docs/flow-lock.md +161 -0
- package/scripts/dispatcher/lib/docs/flow-resolve.md +267 -0
- package/scripts/dispatcher/lib/docs/gate.md +231 -0
- package/scripts/dispatcher/lib/docs/level.md +335 -0
- package/scripts/dispatcher/lib/docs/plan-panel.md +181 -0
- package/scripts/dispatcher/lib/docs/plan.md +200 -0
- package/scripts/dispatcher/lib/docs/planner.md +269 -0
- package/scripts/dispatcher/lib/docs/review.md +255 -0
- package/scripts/dispatcher/lib/docs/reviewer.md +240 -0
- package/scripts/dispatcher/lib/docs/snapshot.md +247 -0
- package/scripts/dispatcher/lib/docs/spec.md +203 -0
- package/scripts/dispatcher/lib/docs/state-store.md +303 -0
- package/scripts/dispatcher/lib/docs/subagent-runner.md +173 -0
- package/scripts/dispatcher/lib/executor.mjs +6 -1
- package/scripts/dispatcher/lib/flow-resolve.mjs +3 -1
- package/scripts/dispatcher/lib/level.mjs +29 -3
- package/scripts/dispatcher/lib/review.mjs +1 -1
- package/scripts/dispatcher/lib/subagent-runner.mjs +5 -3
- package/scripts/docs/auto-rules.md +376 -0
- package/scripts/docs/auto-skills.md +173 -0
- package/scripts/docs/build-agents-commands.md +183 -0
- package/scripts/docs/cli-entry.md +153 -0
- package/scripts/docs/coverage-fix.md +177 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +189 -0
- package/scripts/lib/changed-files.mjs +4 -1
- package/scripts/lib/diff-added-lines.mjs +85 -0
- package/scripts/lib/docs/changed-files.md +149 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +222 -0
- package/scripts/lib/docs/check-reporter.md +175 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +157 -0
- package/scripts/lib/docs/discover-checkable-rules.md +165 -0
- package/scripts/lib/docs/ensure-tool.md +254 -0
- package/scripts/lib/docs/generated-markdown.md +275 -0
- package/scripts/lib/docs/gha-workflow.md +326 -0
- package/scripts/lib/docs/inline-template-links.md +303 -0
- package/scripts/lib/docs/list-rule-ids.md +156 -0
- package/scripts/lib/docs/load-cursor-config.md +147 -0
- package/scripts/lib/docs/mirror-parity.md +167 -0
- package/scripts/lib/worktree.mjs +26 -0
- package/scripts/worktree-cli.mjs +12 -2
- package/skills/coverage-fix/SKILL.md +34 -45
- package/skills/docgen/SKILL.md +44 -23
- package/skills/docgen/bench/etalon/firebase_hosting.md +19 -0
- package/skills/docgen/bench/etalon/k8s-tree.md +24 -0
- package/skills/docgen/bench/etalon/overlay-paths.md +24 -0
- package/skills/docgen/js/docgen-ignore.mjs +54 -0
- package/skills/docgen/js/docgen-scan.mjs +37 -21
- package/skills/llm-patch/SKILL.md +23 -2
- package/skills/start-check/SKILL.md +26 -53
- package/skills/start-check/js/check.mjs +211 -0
- package/skills/taze/SKILL.md +9 -3
- package/skills/taze/js/diff.mjs +154 -0
- package/types/bin/n-cursor.d.ts +1 -1
- package/skills/fix-tests/SKILL.md +0 -119
- package/skills/fix-tests/meta.json +0 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# ensure-nitra-cursor-dev-dependencies.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `ensure-nitra-cursor-dev-dependencies.mjs` забезпечує, що пакет `@nitra/cursor` буде оголошений у `devDependencies` workspace-root `package.json` проєкту, в якому виконується CLI `n-cursor`. Якщо запис відсутній і в `devDependencies`, і в `dependencies`, модуль дописує його з діапазоном `^<version>`, узятим з поля `version` з `package.json` фактично завантаженого пакету `@nitra/cursor`.
|
|
6
|
+
|
|
7
|
+
Призначення: коли користувач викликає `npx @nitra/cursor` (зокрема команду `check`), node-кеш npx містить пакет, але після наступного `bun install` / `npm install` цей кеш може не відтворити пакет у проєкті. Дописавши пакет у `devDependencies` workspace-root, модуль гарантує, що `n-cursor` і його допоміжні скрипти з `node_modules/@nitra/cursor/scripts/` стануть відтвореною частиною проєкту й не залежатимуть від кешу npx.
|
|
8
|
+
|
|
9
|
+
Workspace-root визначається мінімалістично: береться `package.json` поруч зі стартовою директорією (зазвичай `process.cwd()`) і вважається workspace-root, якщо в ньому є поле `workspaces`. Підйом по дереву директорій не виконується.
|
|
10
|
+
|
|
11
|
+
Модуль написаний як ESM (`.mjs`), використовує лише стандартну бібліотеку Node.js, не має сторонніх залежностей і виконує синхронні файлові операції лише для перевірки існування (`existsSync`). Решта IO — асинхронна через `node:fs/promises`.
|
|
12
|
+
|
|
13
|
+
## Експорти / API
|
|
14
|
+
|
|
15
|
+
Модуль експортує дві асинхронні функції:
|
|
16
|
+
|
|
17
|
+
- `readBundledPackageVersion()` — повертає версію встановленого пакету `@nitra/cursor` або `null`.
|
|
18
|
+
- `ensureNitraCursorInRootDevDependencies(root, options?)` — головна точка входу: перевіряє стан проєкту й, за потреби, мутує його `package.json`. Повертає булеве значення про факт запису на диск.
|
|
19
|
+
|
|
20
|
+
Внутрішні (не експортовані) функції модуля:
|
|
21
|
+
|
|
22
|
+
- `readJsonObject(path)` — м’який парсер JSON-обʼєкта з диска.
|
|
23
|
+
- `readAdjacentWorkspaceRootPackageJson(startDir)` — читання `package.json` поряд зі стартовою директорією за умови, що це workspace-root.
|
|
24
|
+
|
|
25
|
+
Константа модульного рівня:
|
|
26
|
+
|
|
27
|
+
- `PACKAGE_NAME` (`'@nitra/cursor'`) — імʼя пакету, який забезпечується у `devDependencies`.
|
|
28
|
+
|
|
29
|
+
Внутрішній стан модуля (обчислюється один раз під час імпорту):
|
|
30
|
+
|
|
31
|
+
- `scriptDir` — абсолютна директорія, в якій лежить сам файл (отримана з `import.meta.url`).
|
|
32
|
+
- `bundledPkgPath` — обчислений шлях до `package.json` пакету `@nitra/cursor`: на один рівень вище `scriptDir`, бо файл лежить у каталозі `scripts/` всередині пакету.
|
|
33
|
+
|
|
34
|
+
## Функції
|
|
35
|
+
|
|
36
|
+
### `readBundledPackageVersion()`
|
|
37
|
+
|
|
38
|
+
Сигнатура: `readBundledPackageVersion(): Promise<string | null>` (експортується).
|
|
39
|
+
|
|
40
|
+
Параметри: відсутні.
|
|
41
|
+
|
|
42
|
+
Поведінка:
|
|
43
|
+
|
|
44
|
+
1. Перевіряє існування файлу `bundledPkgPath` через `existsSync`. Якщо файлу немає — повертає `null` без подальшої роботи.
|
|
45
|
+
2. Читає вміст файлу як UTF-8 через `readFile`.
|
|
46
|
+
3. Парсить вміст як JSON.
|
|
47
|
+
4. Якщо поле `version` у розпарсеному обʼєкті є рядком — повертає його; інакше — повертає `null`.
|
|
48
|
+
5. Будь-яка помилка читання чи парсингу глушиться `try/catch` і дає `null`.
|
|
49
|
+
|
|
50
|
+
Повертає: `Promise<string | null>` — текстова версія (наприклад, `'1.11.14'`) або `null` за відсутності файлу / некоректного JSON / нерядкового `version`.
|
|
51
|
+
|
|
52
|
+
Side effects: лише read-IO (один read від диска). Нічого не пише й не логує.
|
|
53
|
+
|
|
54
|
+
### `readJsonObject(path)`
|
|
55
|
+
|
|
56
|
+
Сигнатура: `readJsonObject(path: string): Promise<Record<string, unknown> | null>` (внутрішня).
|
|
57
|
+
|
|
58
|
+
Параметри:
|
|
59
|
+
|
|
60
|
+
- `path` — абсолютний шлях до JSON-файлу.
|
|
61
|
+
|
|
62
|
+
Поведінка:
|
|
63
|
+
|
|
64
|
+
1. Намагається прочитати файл як UTF-8. Помилка читання повертає `null`.
|
|
65
|
+
2. Намагається розпарсити вміст як JSON. Помилка парсингу повертає `null`.
|
|
66
|
+
3. Перевіряє, що розпарсене значення — це не `null`, типу `object`, і не масив. Якщо умова не виконана — повертає `null`. Інакше повертає сам обʼєкт.
|
|
67
|
+
|
|
68
|
+
Повертає: `Promise<Record<string, unknown> | null>` — JSON-обʼєкт або `null`.
|
|
69
|
+
|
|
70
|
+
Side effects: read-IO.
|
|
71
|
+
|
|
72
|
+
### `readAdjacentWorkspaceRootPackageJson(startDir)`
|
|
73
|
+
|
|
74
|
+
Сигнатура: `readAdjacentWorkspaceRootPackageJson(startDir: string): Promise<{ path: string, pkg: Record<string, unknown> } | null>` (внутрішня).
|
|
75
|
+
|
|
76
|
+
Параметри:
|
|
77
|
+
|
|
78
|
+
- `startDir` — директорія, від якої починається пошук (зазвичай `process.cwd()` процесу CLI).
|
|
79
|
+
|
|
80
|
+
Поведінка:
|
|
81
|
+
|
|
82
|
+
1. Будує `pkgPath = join(startDir, 'package.json')`.
|
|
83
|
+
2. Через `existsSync` перевіряє наявність файлу. Якщо файлу немає — повертає `null`.
|
|
84
|
+
3. Викликає `readJsonObject(pkgPath)`. Якщо результат — не обʼєкт, повертає `null`.
|
|
85
|
+
4. Через `Object.hasOwn(pkg, 'workspaces')` перевіряє наявність поля `workspaces` (саме власне поле, а не з прототипа). Якщо поле є — повертає `{ path, pkg }`; інакше — `null`.
|
|
86
|
+
|
|
87
|
+
Повертає: `Promise<{ path, pkg } | null>` — пара «шлях/обʼєкт» для workspace-root або `null` для не-workspace-root.
|
|
88
|
+
|
|
89
|
+
Side effects: read-IO. Жодних мутацій диска / логів.
|
|
90
|
+
|
|
91
|
+
### `ensureNitraCursorInRootDevDependencies(root, options?)`
|
|
92
|
+
|
|
93
|
+
Сигнатура: `ensureNitraCursorInRootDevDependencies(root: string, options?: { bundledVersion?: string | null, silent?: boolean }): Promise<boolean>` (експортується).
|
|
94
|
+
|
|
95
|
+
Параметри:
|
|
96
|
+
|
|
97
|
+
- `root` — стартова директорія проєкту, зазвичай `process.cwd()` процесу CLI `n-cursor`.
|
|
98
|
+
- `options` — необовʼязковий обʼєкт:
|
|
99
|
+
- `bundledVersion` — попередньо задана версія для тестів; якщо передано — використовується замість виклику `readBundledPackageVersion()`. `null` тут інтерпретується як «викликати fallback» через оператор `??`.
|
|
100
|
+
- `silent` — якщо `true`, не друкувати повідомлення про оновлення у `stdout`.
|
|
101
|
+
|
|
102
|
+
Алгоритм:
|
|
103
|
+
|
|
104
|
+
1. Викликає `readAdjacentWorkspaceRootPackageJson(root)`. Якщо результат `null` (немає workspace-root) — повертає `false`.
|
|
105
|
+
2. Деструктурує `{ path: pkgPath, pkg }`.
|
|
106
|
+
3. Якщо в `pkg.devDependencies` (за умови, що це обʼєкт) присутній ключ `PACKAGE_NAME` — повертає `false` (вже є).
|
|
107
|
+
4. Якщо в `pkg.dependencies` (за умови, що це обʼєкт) присутній ключ `PACKAGE_NAME` — повертає `false` (вже є в runtime deps; додавати дубль до dev не потрібно).
|
|
108
|
+
5. Визначає версію: `options.bundledVersion ?? await readBundledPackageVersion()`. Якщо результат фолсі (`null`, порожній рядок) — повертає `false`.
|
|
109
|
+
6. Гарантує, що `pkg.devDependencies` — це валідний обʼєкт. Якщо там відсутнє поле, `null`, не-обʼєкт або масив — перезаписує його на `{}`.
|
|
110
|
+
7. Записує `pkg.devDependencies[PACKAGE_NAME] = ` `^<ver>`.
|
|
111
|
+
8. Серіалізує `pkg` з відступом 2 пробіли через `JSON.stringify(pkg, null, 2)`, додає завершальний перевід рядка `\n`, пише через `writeFile` у `pkgPath` у UTF-8.
|
|
112
|
+
9. Якщо `options.silent` не встановлено — друкує `📝 Додано <PACKAGE_NAME>@^<ver> у devDependencies у package.json\n` у `stdout` через `console.log`.
|
|
113
|
+
10. Повертає `true`.
|
|
114
|
+
|
|
115
|
+
Повертає: `Promise<boolean>` — `true`, якщо `package.json` дійсно змінено на диску; `false` у всіх no-op-ситуаціях (немає workspace-root, пакет вже задекларовано, версія недоступна).
|
|
116
|
+
|
|
117
|
+
Side effects:
|
|
118
|
+
|
|
119
|
+
- Читання `package.json` workspace-root.
|
|
120
|
+
- Можливий запис `package.json` workspace-root (мутація вмісту).
|
|
121
|
+
- Можливий запис у `stdout` через `console.log` (можна заглушити через `options.silent`).
|
|
122
|
+
|
|
123
|
+
Особливості:
|
|
124
|
+
|
|
125
|
+
- Перевірка «вже є в deps» поблажлива: якщо `devDependencies` / `dependencies` присутнє, але не є обʼєктом (некоректний `package.json`), модуль трактує це як «нема» і йде далі.
|
|
126
|
+
- Перезапис `pkg.devDependencies` на `{}` у випадку, коли поле не є обʼєктом, спрямований на корекцію некоректного стану `package.json`. Це означає, що нечитабельне поле буде втрачено.
|
|
127
|
+
- Серіалізація використовує `JSON.stringify` із відступом 2 і завершальним `\n`. Будь-яке стилістичне форматування з оригіналу (наприклад, табуляції чи tab-width=4) буде нормалізоване до 2 пробілів.
|
|
128
|
+
|
|
129
|
+
## Залежності
|
|
130
|
+
|
|
131
|
+
Зовнішніх npm-залежностей немає. Використовуються лише вбудовані модулі Node.js:
|
|
132
|
+
|
|
133
|
+
- `node:fs`
|
|
134
|
+
- `existsSync` — синхронна перевірка існування файлу.
|
|
135
|
+
- `node:fs/promises`
|
|
136
|
+
- `readFile` — асинхронне читання файлу як UTF-8.
|
|
137
|
+
- `writeFile` — асинхронний запис файлу як UTF-8.
|
|
138
|
+
- `node:path`
|
|
139
|
+
- `dirname` — отримати директорію з шляху.
|
|
140
|
+
- `join` — побудувати шлях.
|
|
141
|
+
- `node:url`
|
|
142
|
+
- `fileURLToPath` — конвертувати `file://`-URL у файловий шлях.
|
|
143
|
+
|
|
144
|
+
Зовнішні споживачі модуля:
|
|
145
|
+
|
|
146
|
+
- CLI `@nitra/cursor` (`bin`), що викликає `ensureNitraCursorInRootDevDependencies(process.cwd())` під час кожного запуску (зокрема перед `check`), щоб довести стан `package.json` проєкту до бажаного.
|
|
147
|
+
- Тестовий код може передавати `bundledVersion` у `options`, щоб не залежати від реальної версії з диска.
|
|
148
|
+
|
|
149
|
+
## Потік виконання / Використання
|
|
150
|
+
|
|
151
|
+
Типовий потік під час `npx @nitra/cursor check` у workspace-root:
|
|
152
|
+
|
|
153
|
+
1. CLI імпортує модуль і викликає `await ensureNitraCursorInRootDevDependencies(process.cwd())`.
|
|
154
|
+
2. Модуль читає `<cwd>/package.json`. Якщо там немає поля `workspaces` — це не workspace-root, повертається `false`, файл не змінюється.
|
|
155
|
+
3. Якщо `package.json` має `workspaces` і вже містить `@nitra/cursor` у `devDependencies` або `dependencies` — повертається `false`, файл не змінюється.
|
|
156
|
+
4. Інакше модуль читає `version` з `package.json` встановленого пакету `@nitra/cursor` (на рівень вище за `scripts/`), формує діапазон `^<version>` і записує його в `pkg.devDependencies['@nitra/cursor']`.
|
|
157
|
+
5. Серіалізований JSON записується назад у `<cwd>/package.json`.
|
|
158
|
+
6. У `stdout` логується повідомлення про додавання (якщо не задано `silent: true`).
|
|
159
|
+
7. Повертається `true`.
|
|
160
|
+
|
|
161
|
+
Приклад використання:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
import { ensureNitraCursorInRootDevDependencies } from '@nitra/cursor/scripts/ensure-nitra-cursor-dev-dependencies.mjs'
|
|
165
|
+
|
|
166
|
+
const changed = await ensureNitraCursorInRootDevDependencies(process.cwd())
|
|
167
|
+
if (changed) {
|
|
168
|
+
// package.json було оновлено — можна підказати користувачу зробити bun install
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Приклад використання з тестового сетапу:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
|
|
176
|
+
|
|
177
|
+
await ensureNitraCursorInRootDevDependencies(tmpDir, {
|
|
178
|
+
bundledVersion: '9.9.9',
|
|
179
|
+
silent: true
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Передумови, на які покладається модуль:
|
|
184
|
+
|
|
185
|
+
- Файл `ensure-nitra-cursor-dev-dependencies.mjs` лежить у `<package>/scripts/`, а `package.json` пакету `@nitra/cursor` — у `<package>/package.json`. Якщо структуру переміщено, шлях `bundledPkgPath` стане некоректним.
|
|
186
|
+
- Робоче дерево, передане як `root`, має містити `package.json` із полем `workspaces`. Якщо CLI стартовано всередині workspace-пакету (а не в корені) — модуль нічого не зробить (`false`).
|
|
187
|
+
- Поле `version` у `package.json` пакету `@nitra/cursor` має бути рядком; інакше додавання не відбудеться.
|
|
188
|
+
|
|
189
|
+
Інваріант: повторні виклики модуля у тому самому проєкті стають no-op (другий виклик бачить `@nitra/cursor` у `devDependencies` і повертає `false`).
|
|
@@ -15,7 +15,10 @@ import { spawnSync } from 'node:child_process'
|
|
|
15
15
|
function gitLines(args, cwd) {
|
|
16
16
|
const r = spawnSync('git', args, { cwd, encoding: 'utf8' })
|
|
17
17
|
if (r.status !== 0 || r.error) return []
|
|
18
|
-
return r.stdout
|
|
18
|
+
return r.stdout
|
|
19
|
+
.split('\n')
|
|
20
|
+
.map(s => s.trim())
|
|
21
|
+
.filter(Boolean)
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Додані/змінені рядки на файл (vs HEAD) — для класифікації lint-findings на
|
|
3
|
+
* introduced (рядок у diff) vs pre-existing (поза diff), беклог #6.
|
|
4
|
+
*
|
|
5
|
+
* Парсимо `git diff --unified=0 HEAD -- <files>`: hunk-заголовок `@@ -a,b +c,d @@`
|
|
6
|
+
* дає додані рядки c..c+d-1. Untracked (нові, поза HEAD) — усі рядки introduced (маркер `ALL`).
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from 'node:child_process'
|
|
9
|
+
|
|
10
|
+
/** Маркер «усі рядки файлу introduced» (новий untracked-файл). */
|
|
11
|
+
export const ALL_LINES = 'ALL'
|
|
12
|
+
|
|
13
|
+
/** Шлях цільового файлу у рядку `+++ b/path` (або `/dev/null`). */
|
|
14
|
+
const PLUS_FILE_RE = /^\+\+\+ (?:b\/)?(.*)$/u
|
|
15
|
+
/** Діапазон доданих рядків у hunk-заголовку `@@ -a,b +c,d @@`. */
|
|
16
|
+
const HUNK_ADD_RE = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/u
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Парсить вивід `git diff --unified=0` у мапу доданих рядків на файл.
|
|
20
|
+
* @param {string} diffText сирий вивід git diff
|
|
21
|
+
* @returns {Map<string, Set<number>>} файл → множина доданих рядків
|
|
22
|
+
*/
|
|
23
|
+
export function parseAddedLines(diffText) {
|
|
24
|
+
const byFile = new Map()
|
|
25
|
+
let current = null
|
|
26
|
+
for (const line of String(diffText).split('\n')) {
|
|
27
|
+
const fileMatch = PLUS_FILE_RE.exec(line)
|
|
28
|
+
if (fileMatch) {
|
|
29
|
+
current = fileMatch[1] === '/dev/null' ? null : fileMatch[1]
|
|
30
|
+
if (current && !byFile.has(current)) byFile.set(current, new Set())
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
const hunk = current && HUNK_ADD_RE.exec(line)
|
|
34
|
+
if (hunk) {
|
|
35
|
+
const start = Number(hunk[1])
|
|
36
|
+
const count = hunk[2] === undefined ? 1 : Number(hunk[2])
|
|
37
|
+
for (let i = 0; i < count; i++) byFile.get(current).add(start + i)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return byFile
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Тихий git → stdout або `''`.
|
|
45
|
+
* @param {string[]} args аргументи git
|
|
46
|
+
* @param {string} cwd робочий каталог
|
|
47
|
+
* @returns {string} stdout
|
|
48
|
+
*/
|
|
49
|
+
function git(args, cwd) {
|
|
50
|
+
const r = spawnSync('git', args, { cwd, encoding: 'utf8' })
|
|
51
|
+
return r.status === 0 ? (r.stdout ?? '') : ''
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Додані рядки на файл (vs HEAD) для заданих файлів. Tracked → з diff;
|
|
56
|
+
* untracked (нові) → маркер `ALL_LINES`.
|
|
57
|
+
* @param {string[]} files відносні шляхи (від cwd)
|
|
58
|
+
* @param {string} [cwd] корінь репо
|
|
59
|
+
* @param {{ git?: (args: string[], cwd: string) => string }} [deps] ін'єкція git (тести)
|
|
60
|
+
* @returns {Map<string, Set<number> | typeof ALL_LINES>} файл → додані рядки / `ALL`
|
|
61
|
+
*/
|
|
62
|
+
export function addedLinesByFile(files, cwd = process.cwd(), deps = {}) {
|
|
63
|
+
if (!files || files.length === 0) return new Map()
|
|
64
|
+
const run = deps.git ?? git
|
|
65
|
+
const map = parseAddedLines(run(['diff', '--unified=0', 'HEAD', '--', ...files], cwd))
|
|
66
|
+
const untracked = run(['ls-files', '--others', '--exclude-standard', '--', ...files], cwd)
|
|
67
|
+
for (const f of untracked.split('\n').filter(Boolean)) {
|
|
68
|
+
map.set(f, ALL_LINES)
|
|
69
|
+
}
|
|
70
|
+
return map
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Чи рядок `line` у файлі `file` — доданий (introduced).
|
|
75
|
+
* @param {Map<string, Set<number> | typeof ALL_LINES>} addedLines результат `addedLinesByFile`
|
|
76
|
+
* @param {string} file відносний шлях
|
|
77
|
+
* @param {number} line номер рядка
|
|
78
|
+
* @returns {boolean} результат
|
|
79
|
+
*/
|
|
80
|
+
export function isIntroducedLine(addedLines, file, line) {
|
|
81
|
+
const entry = addedLines.get(file)
|
|
82
|
+
if (entry === undefined) return false
|
|
83
|
+
if (entry === ALL_LINES) return true
|
|
84
|
+
return entry.has(line)
|
|
85
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# changed-files.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `changed-files.mjs` — допоміжна бібліотека для збору переліку змінених файлів у робочому дереві git-репозиторію. Використовується lint-оркестратором у quick-режимі та `coverage --changed` для визначення scope-у файлів, на яких потрібно прогнати перевірки.
|
|
6
|
+
|
|
7
|
+
Логіка модуля:
|
|
8
|
+
|
|
9
|
+
- збирає tracked-modified та staged файли через `git diff` (з `--diff-filter=ACMR`, тобто Added/Copied/Modified/Renamed — без Deleted);
|
|
10
|
+
- додає untracked файли через `git ls-files --others --exclude-standard` (з повагою до `.gitignore`);
|
|
11
|
+
- дедуплікує об'єднаний список через `Set`;
|
|
12
|
+
- повертає relative-posix шляхи відносно `cwd`;
|
|
13
|
+
- поза git-репо або при помилці git мовчки повертає порожній список (для `gitLines`);
|
|
14
|
+
- для режиму "since base" — fail-closed: якщо базовий комміт недосяжний, кидає явну помилку, щоб gate не пройшов мовчки.
|
|
15
|
+
|
|
16
|
+
Видалені файли свідомо не включаються — лінтити неіснуючий файл немає сенсу.
|
|
17
|
+
|
|
18
|
+
## Експорти / API
|
|
19
|
+
|
|
20
|
+
Модуль експортує дві функції:
|
|
21
|
+
|
|
22
|
+
| Експорт | Тип | Призначення |
|
|
23
|
+
| -------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| `collectChangedFiles` | `(cwd?: string) => string[]` | Список змінених + untracked файлів робочого дерева відносно `HEAD`. |
|
|
25
|
+
| `collectChangedFilesSince` | `(base: string \| null, cwd?: string) => string[]` | Список змінених + untracked файлів **відносно довільного базового комміту**. Без `base` — fallback на `collectChangedFiles`. |
|
|
26
|
+
|
|
27
|
+
Внутрішня (неекспортована) функція:
|
|
28
|
+
|
|
29
|
+
| Внутрішнє | Тип | Призначення |
|
|
30
|
+
| ---------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `gitLines` | `(args: string[], cwd: string) => string[]` | Виконує `git <args>` у `cwd` і повертає непорожні trim-нуті рядки stdout або `[]` при помилці. |
|
|
32
|
+
|
|
33
|
+
## Функції
|
|
34
|
+
|
|
35
|
+
### `gitLines(args, cwd)` (internal)
|
|
36
|
+
|
|
37
|
+
Виклик git-команди з парсингом stdout у масив рядків.
|
|
38
|
+
|
|
39
|
+
- **Сигнатура:** `function gitLines(args: string[], cwd: string): string[]`
|
|
40
|
+
- **Параметри:**
|
|
41
|
+
- `args` — масив аргументів для `git` (наприклад `['diff', 'HEAD', '--name-only']`).
|
|
42
|
+
- `cwd` — робоча директорія для процесу `git`.
|
|
43
|
+
- **Повертає:** масив непорожніх рядків stdout (після `trim`). Якщо `r.status !== 0` або є `r.error` — повертає `[]`.
|
|
44
|
+
- **Side effects:**
|
|
45
|
+
- Синхронно спавнить процес `git` через `spawnSync` з `node:child_process`;
|
|
46
|
+
- не пише в stdout/stderr батьківського процесу (всі потоки збираються через `encoding: 'utf8'`);
|
|
47
|
+
- не кидає винятків — fail-silent.
|
|
48
|
+
|
|
49
|
+
### `collectChangedFiles(cwd?)`
|
|
50
|
+
|
|
51
|
+
Збирає список змінених + untracked файлів робочого дерева відносно `HEAD`.
|
|
52
|
+
|
|
53
|
+
- **Сигнатура:** `function collectChangedFiles(cwd?: string): string[]`
|
|
54
|
+
- **Параметри:**
|
|
55
|
+
- `cwd` (опційно, дефолт `process.cwd()`) — корінь git-репо.
|
|
56
|
+
- **Алгоритм:**
|
|
57
|
+
1. `git diff HEAD --name-only --diff-filter=ACMR` — повертає всі tracked файли, які відрізняються від `HEAD` (staged + unstaged), фільтр `ACMR` відсікає Deleted.
|
|
58
|
+
2. `git ls-files --others --exclude-standard` — повертає untracked файли, які не ігноруються `.gitignore`.
|
|
59
|
+
3. Об'єднання обох списків через `new Set([...modified, ...untracked])` для дедуплікації.
|
|
60
|
+
- **Повертає:** `string[]` — унікальні relative-posix шляхи без видалених файлів.
|
|
61
|
+
- **Поведінка поза git-репо / при помилці:** `gitLines` мовчки повертає `[]` для обох викликів, тож результат — порожній масив.
|
|
62
|
+
|
|
63
|
+
### `collectChangedFilesSince(base, cwd?)`
|
|
64
|
+
|
|
65
|
+
Збирає список змінених + untracked файлів відносно довільного базового комміту. Призначено для сценаріїв, де базовий комміт зафіксовано у стані flow-турнікета (executor комітить кожен крок, тож потрібно ловити зміни «від base», а не «від HEAD»).
|
|
66
|
+
|
|
67
|
+
- **Сигнатура:** `function collectChangedFilesSince(base: string | null, cwd?: string): string[]`
|
|
68
|
+
- **Параметри:**
|
|
69
|
+
- `base` — SHA/ref базового комміту (типово `metadata.base_commit` зі стану flow). Якщо `null`/`undefined`/порожній — fallback на `collectChangedFiles(cwd)`.
|
|
70
|
+
- `cwd` (опційно, дефолт `process.cwd()`) — корінь git-репо.
|
|
71
|
+
- **Алгоритм:**
|
|
72
|
+
1. Якщо `base` falsy — повертає результат `collectChangedFiles(cwd)`.
|
|
73
|
+
2. Перевіряє досяжність base через `git rev-parse --verify --quiet <base>^{commit}`.
|
|
74
|
+
3. Якщо verify не успішний (`status !== 0` або `error`) — **кидає `Error`** з повідомленням: `collectChangedFilesSince: base-комміт «<base>» недосяжний у <cwd> (rebase/force-update?) — coverage --changed не може визначити scope`.
|
|
75
|
+
4. `git diff <base> --name-only --diff-filter=ACMR` — **без** `..`/`...`, **без** `HEAD`. Така форма порівнює base-комміт із поточним **робочим деревом**, тобто ловить одночасно: закомічене від base, staged та незакомічені модифікації.
|
|
76
|
+
5. `git ls-files --others --exclude-standard` — untracked файли (як у `collectChangedFiles`).
|
|
77
|
+
6. Дедуплікація через `Set`.
|
|
78
|
+
- **Повертає:** `string[]` — унікальні relative-posix шляхи без видалених файлів.
|
|
79
|
+
- **Контракт fail-closed:** на відміну від `collectChangedFiles`, ця функція **навмисно** не маскує помилку недосяжного base. Інакше `git diff` повернув би exit 128, `gitLines` дав би `[]`, і gate мовчки пройшов би без перевірки — це порушує безпеку coverage-перевірки.
|
|
80
|
+
- **Side effects:**
|
|
81
|
+
- спавнить два-три git-процеси (один verify + два data-збори, або один fallback);
|
|
82
|
+
- може кинути `Error` (єдина точка throw у модулі).
|
|
83
|
+
|
|
84
|
+
## Залежності
|
|
85
|
+
|
|
86
|
+
### Зовнішні (built-in Node.js)
|
|
87
|
+
|
|
88
|
+
- `node:child_process` — імпорт `spawnSync` для синхронного виклику git-команд із захопленням stdout.
|
|
89
|
+
|
|
90
|
+
### Системні (runtime)
|
|
91
|
+
|
|
92
|
+
- `git` — має бути доступний у `PATH` процесу. За відсутності `gitLines` поверне `[]` (через `r.error`/ненульовий статус).
|
|
93
|
+
|
|
94
|
+
### Внутрішні модулі
|
|
95
|
+
|
|
96
|
+
Файл не імпортує жодного локального модуля проєкту (zero-dependency бібліотечний листок).
|
|
97
|
+
|
|
98
|
+
## Потік виконання / Використання
|
|
99
|
+
|
|
100
|
+
### Quick-lint (lint-оркестратор)
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
import { collectChangedFiles } from './changed-files.mjs'
|
|
104
|
+
|
|
105
|
+
const files = collectChangedFiles()
|
|
106
|
+
// → лінт тільки цих файлів, замість обходу всього монорепо
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Quick-режим прагне мінімізувати IO: лінтить лише те, що користувач щойно змінив у дереві (modified + staged + untracked), ігноруючи решту репо.
|
|
110
|
+
|
|
111
|
+
### Coverage / flow-турнікет
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
import { collectChangedFilesSince } from './changed-files.mjs'
|
|
115
|
+
|
|
116
|
+
const base = metadata.base_commit // SHA, зафіксований при старті flow-кроку
|
|
117
|
+
const scope = collectChangedFilesSince(base, repoRoot)
|
|
118
|
+
// → файли, які змінилися з моменту base, незалежно від того,
|
|
119
|
+
// чи їх вже закомічено в проміжних кроках executor-а
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Чому саме `git diff <base>` без `..`/`...`:
|
|
123
|
+
|
|
124
|
+
- `git diff A..B` — порівнює дерева A та B, **ігнорує** робоче дерево;
|
|
125
|
+
- `git diff A...B` — порівнює B з merge-base(A,B), теж ігнорує робоче дерево;
|
|
126
|
+
- `git diff <base>` (один аргумент) — порівнює `<base>` із **робочим деревом** (включно з unstaged змінами).
|
|
127
|
+
|
|
128
|
+
Це критично для flow-турнікета, де executor може як комітити проміжні зміни, так і залишати їх unstaged — gate повинен побачити всі зміни від `base` однаково.
|
|
129
|
+
|
|
130
|
+
### Поведінка при недосяжному base
|
|
131
|
+
|
|
132
|
+
Якщо у репозиторії стався rebase, force-push або shallow clone обрізав історію — `<base>` може стати недосяжним. У такому разі:
|
|
133
|
+
|
|
134
|
+
- `collectChangedFiles` (без base) — поведінка незмінна, працює від `HEAD`;
|
|
135
|
+
- `collectChangedFilesSince(base, cwd)` — **кидає Error** до того, як викликати `git diff`, щоб coverage-gate явно впав, а не пройшов на порожньому scope.
|
|
136
|
+
|
|
137
|
+
### Формат шляхів
|
|
138
|
+
|
|
139
|
+
Усі повернені шляхи — POSIX-style relative до `cwd` (як їх віддає git за замовчуванням). На Windows git також віддає forward slashes, тож додаткової нормалізації немає.
|
|
140
|
+
|
|
141
|
+
### Точки помилок (summary)
|
|
142
|
+
|
|
143
|
+
| Сценарій | Поведінка |
|
|
144
|
+
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| Поза git-репо | `collectChangedFiles` → `[]`; `collectChangedFilesSince(null)` → `[]`; `collectChangedFilesSince('SHA')` → `Error` (verify впаде). |
|
|
146
|
+
| `git` відсутній у PATH | Усі виклики `gitLines` → `[]`; `collectChangedFilesSince` із `base` → `Error` (verify впаде). |
|
|
147
|
+
| `base` досяжний | `collectChangedFilesSince` повертає список (можливо порожній). |
|
|
148
|
+
| `base` недосяжний (rebase/force-push/shallow) | `collectChangedFilesSince` кидає `Error`. |
|
|
149
|
+
| `base` falsy (`null`/`undefined`/`''`/`0`) | `collectChangedFilesSince` → результат `collectChangedFiles(cwd)`. |
|