@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,296 @@
|
|
|
1
|
+
# `trace.mjs` — наскрізна простежуваність артефактів документації
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль реалізує CLI-команду `n-cursor trace` (специфікація §5.4 / §7) — інструмент **наскрізної простежуваності** (traceability) між артефактами в `docs/`. Він читає YAML-front-matter з усіх Markdown-файлів у каталогах `docs/tasks`, `docs/specs`, `docs/plans`, `docs/adr`, будує ланцюг зв'язків між ними за полями-лінками (`adr`, `spec`, `plan`, `flow`, `change`, `task`) і **флагує розриви** — тобто посилання на неіснуючі файли.
|
|
6
|
+
|
|
7
|
+
Модуль повністю **read-only**: жодних мутацій файлової системи. Підтримує два режими виводу:
|
|
8
|
+
|
|
9
|
+
- **текстовий** (за замовчуванням) — людино-читабельний звіт з символами `→`/`✗`/`~`;
|
|
10
|
+
- **JSON** (`--json`) — machine-readable структура для CI / інших інструментів.
|
|
11
|
+
|
|
12
|
+
Поведінка щодо FS повністю інжектабельна (`readdir`, `readFile`, `exists`, `cwd`, `log`), завдяки чому модуль тестується без реального диска та без зміни робочої директорії.
|
|
13
|
+
|
|
14
|
+
Окремий нюанс — поле `flow` трактується як **інформаційне**, а не chain-поле: воно вказує на runtime-стан у `.worktrees/<branch>.flow.json`, який gitignored і за межами `docs/`, тому його відсутність ніколи не вважається розривом ланцюга (інакше у чистому checkout або CI-сесії був би хибний сигнал).
|
|
15
|
+
|
|
16
|
+
## Експорти / API
|
|
17
|
+
|
|
18
|
+
| Експорт | Тип | Призначення |
|
|
19
|
+
| ----------------------------- | -------------- | ------------------------------------------------------------ |
|
|
20
|
+
| `parseFrontMatter(content)` | named function | Парсить плаский YAML-front-matter Markdown-файла. |
|
|
21
|
+
| `analyze(artifacts, resolve)` | named function | Будує аналіз лінків артефактів зі статусами `ok`/`breaking`. |
|
|
22
|
+
| `render(analysis)` | named function | Текстовий рендер результату `analyze`. |
|
|
23
|
+
| `runTraceCli(args, deps?)` | named function | Точка входу CLI `n-cursor trace [--json]`. |
|
|
24
|
+
|
|
25
|
+
Внутрішні (не експортуються): `isSimpleKey`, `resolveLink`, `renderLink`, константи `LINK_FIELDS`, `INFO_LINK_FIELDS`, `DIRS`.
|
|
26
|
+
|
|
27
|
+
### Константи модуля
|
|
28
|
+
|
|
29
|
+
- `LINK_FIELDS = ['adr', 'spec', 'plan', 'flow', 'change', 'task']` — впорядкований список полів front-matter, які розглядаються як лінки. Порядок впливає на порядок виводу.
|
|
30
|
+
- `INFO_LINK_FIELDS = new Set(['flow'])` — підмножина полів, відсутність яких **не** рве ланцюг (інформаційні, не breaking).
|
|
31
|
+
- `DIRS = ['docs/tasks', 'docs/specs', 'docs/plans', 'docs/adr']` — каталоги, у яких шукаються traceable-артефакти.
|
|
32
|
+
|
|
33
|
+
## Функції
|
|
34
|
+
|
|
35
|
+
### `parseFrontMatter(content)`
|
|
36
|
+
|
|
37
|
+
**Сигнатура:** `parseFrontMatter(content: string): Record<string, string | null> | null`
|
|
38
|
+
|
|
39
|
+
**Параметри:**
|
|
40
|
+
|
|
41
|
+
- `content` — повний текст Markdown-файла.
|
|
42
|
+
|
|
43
|
+
**Повертає:**
|
|
44
|
+
|
|
45
|
+
- Об'єкт `{ key: value }` зі значеннями типу `string` (звичайне поле) або `null` (порожнє чи літерал `null`);
|
|
46
|
+
- `null`, якщо файл не починається з `---` або немає закриваючого `\n---`.
|
|
47
|
+
|
|
48
|
+
**Логіка:**
|
|
49
|
+
|
|
50
|
+
1. Перевіряє, що файл починається з `---`. Інакше — `null`.
|
|
51
|
+
2. Шукає закриваючий маркер `\n---` починаючи з 4-ї позиції. Якщо нема — `null`.
|
|
52
|
+
3. Розбиває блок front-matter на рядки.
|
|
53
|
+
4. Для кожного рядка: знаходить перше `:`, ділить на `key` / `val`.
|
|
54
|
+
5. Відсікає `key`, який не є простим ідентифікатором (`isSimpleKey`) — тобто рядки з вкладеними структурами, дефісами, цифрами тощо ігноруються.
|
|
55
|
+
6. Відрізає інлайн-коментар у форматі ` #…` (пробіл-решітка).
|
|
56
|
+
7. Тримує значення, прибирає одиничні зовнішні лапки `"`/`'`.
|
|
57
|
+
8. Якщо значення порожнє або дорівнює рядку `'null'` — нормалізує у `null`.
|
|
58
|
+
|
|
59
|
+
**Side effects:** немає (чиста функція).
|
|
60
|
+
|
|
61
|
+
### `isSimpleKey(key)` (внутрішня)
|
|
62
|
+
|
|
63
|
+
**Сигнатура:** `isSimpleKey(key: string): boolean`
|
|
64
|
+
|
|
65
|
+
**Параметри:** `key` — потенційний ключ front-matter.
|
|
66
|
+
|
|
67
|
+
**Повертає:** `true`, якщо ключ непорожній і складається тільки з літер `a–z`/`A–Z` та підкреслень. Регулярний вираз: `/[a-z_]/iu` для кожного символу.
|
|
68
|
+
|
|
69
|
+
**Side effects:** немає.
|
|
70
|
+
|
|
71
|
+
**Призначення:** захист від спроби парсити вкладені структури або не-ідентифікатори (рядки з `-`, цифрами, `.`, тощо).
|
|
72
|
+
|
|
73
|
+
### `analyze(artifacts, resolve)`
|
|
74
|
+
|
|
75
|
+
**Сигнатура:**
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
analyze(
|
|
79
|
+
artifacts: { file: string, fm: Record<string, string | null> }[],
|
|
80
|
+
resolve: (target: string, artifactFile: string) => boolean
|
|
81
|
+
): {
|
|
82
|
+
file: string,
|
|
83
|
+
kind: string | null,
|
|
84
|
+
id: string | null,
|
|
85
|
+
status: string | null,
|
|
86
|
+
links: { field: string, target: string, ok: boolean, breaking: boolean }[]
|
|
87
|
+
}[]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Параметри:**
|
|
91
|
+
|
|
92
|
+
- `artifacts` — масив пар `{ file, fm }`: відносний шлях артефакту та розпарсений front-matter.
|
|
93
|
+
- `resolve` — предикат, що повертає `true`, якщо лінк-цільовий файл існує (зазвичай — bound-`resolveLink`).
|
|
94
|
+
|
|
95
|
+
**Повертає:** масив аналізованих артефактів. Для кожного:
|
|
96
|
+
|
|
97
|
+
- `kind`, `id`, `status` — з front-matter (або `null`);
|
|
98
|
+
- `links` — масив об'єктів про кожен наявний лінк:
|
|
99
|
+
- `field` — назва поля (один з `LINK_FIELDS`);
|
|
100
|
+
- `target` — значення лінка;
|
|
101
|
+
- `ok` — чи резолвиться цільовий файл;
|
|
102
|
+
- `breaking` — `false`, якщо поле в `INFO_LINK_FIELDS` (зараз — `flow`); `true` для всіх інших.
|
|
103
|
+
|
|
104
|
+
**Логіка:** проходить `LINK_FIELDS` у фіксованому порядку, бере тільки поля, наявні у `fm` з truthy-значенням.
|
|
105
|
+
|
|
106
|
+
**Side effects:** немає (логіка чиста, `resolve` інкапсулює I/O).
|
|
107
|
+
|
|
108
|
+
### `resolveLink(root, artifactFile, target, exists)` (внутрішня)
|
|
109
|
+
|
|
110
|
+
**Сигнатура:** `resolveLink(root: string, artifactFile: string, target: string, exists: (absPath: string) => boolean): boolean`
|
|
111
|
+
|
|
112
|
+
**Параметри:**
|
|
113
|
+
|
|
114
|
+
- `root` — абсолютний шлях кореня репо;
|
|
115
|
+
- `artifactFile` — rel-шлях артефакту (напр. `docs/plans/x.md`);
|
|
116
|
+
- `target` — значення лінка з front-matter;
|
|
117
|
+
- `exists` — інжекторована перевірка існування файла.
|
|
118
|
+
|
|
119
|
+
**Повертає:** `true`, якщо `target` резолвиться **або** відносно теки артефакту (`<root>/<dirname(artifactFile)>/<target>`), **або** root-relative (`<root>/<target>`). Обидві форми вважаються валідними — це конвенція документації (`../specs/…` чи `docs/specs/…`).
|
|
120
|
+
|
|
121
|
+
**Side effects:** виклик `exists` (зовнішнє I/O), але інкапсульовано — модуль сам диск не чіпає.
|
|
122
|
+
|
|
123
|
+
### `render(analysis)`
|
|
124
|
+
|
|
125
|
+
**Сигнатура:** `render(analysis: ReturnType<typeof analyze>): string`
|
|
126
|
+
|
|
127
|
+
**Параметри:** `analysis` — результат `analyze`.
|
|
128
|
+
|
|
129
|
+
**Повертає:** багаторядковий текст:
|
|
130
|
+
|
|
131
|
+
- Якщо `analysis` порожній — рядок `'trace: артефактів із front-matter не знайдено'`.
|
|
132
|
+
- Інакше для кожного артефакту:
|
|
133
|
+
- заголовок виду `${kind} · ${id ?? file} [${status ?? '—'}]`;
|
|
134
|
+
- вкладені рядки лінків (через `renderLink`), з відступом 3 пробіли.
|
|
135
|
+
|
|
136
|
+
**Side effects:** немає.
|
|
137
|
+
|
|
138
|
+
### `renderLink(l)` (внутрішня)
|
|
139
|
+
|
|
140
|
+
**Сигнатура:** `renderLink(l: { field: string, target: string, ok: boolean, breaking: boolean }): string`
|
|
141
|
+
|
|
142
|
+
**Повертає:** один з трьох форматів:
|
|
143
|
+
|
|
144
|
+
- `→ <field>: <target>` — резолвлено успішно (`l.ok === true`);
|
|
145
|
+
- `✗ <field>: <target> (РОЗРИВ — файл відсутній)` — нерезолвлене chain-поле (`breaking && !ok`);
|
|
146
|
+
- `~ <field>: <target> (runtime-стан — не рве ланцюг)` — нерезолвлене info-поле (`!breaking && !ok`, наприклад `flow`).
|
|
147
|
+
|
|
148
|
+
**Side effects:** немає.
|
|
149
|
+
|
|
150
|
+
### `runTraceCli(args, deps?)`
|
|
151
|
+
|
|
152
|
+
**Сигнатура:**
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
runTraceCli(
|
|
156
|
+
args: string[],
|
|
157
|
+
deps?: {
|
|
158
|
+
cwd?: string,
|
|
159
|
+
readdir?: (dir: string) => string[],
|
|
160
|
+
readFile?: (file: string) => string,
|
|
161
|
+
exists?: (file: string) => boolean,
|
|
162
|
+
log?: (m: string) => void
|
|
163
|
+
}
|
|
164
|
+
): number
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Параметри:**
|
|
168
|
+
|
|
169
|
+
- `args` — CLI-аргументи (підтримується тільки `--json`);
|
|
170
|
+
- `deps` — інжектовані залежності для тестування. Дефолти:
|
|
171
|
+
- `cwd` → `process.cwd()`;
|
|
172
|
+
- `readdir` → `readdirSync` з охороною `existsSync` (повертає `[]`, якщо каталога нема);
|
|
173
|
+
- `readFile` → `readFileSync(file, 'utf8')`;
|
|
174
|
+
- `exists` → `existsSync`;
|
|
175
|
+
- `log` → `console.log`.
|
|
176
|
+
|
|
177
|
+
**Повертає:** exit code:
|
|
178
|
+
|
|
179
|
+
- `0` — ланцюг цілісний (немає breaking-розривів);
|
|
180
|
+
- `1` — є хоча б один breaking-лінк, що не резолвиться.
|
|
181
|
+
|
|
182
|
+
**Side effects:**
|
|
183
|
+
|
|
184
|
+
- Читання FS через `readdir` / `readFile` / `exists` (інжектабельне).
|
|
185
|
+
- Виклик `log` — за замовчуванням друк у `stdout`.
|
|
186
|
+
- Жодних мутацій FS, мережі або стану процесу.
|
|
187
|
+
|
|
188
|
+
**Логіка покроково:**
|
|
189
|
+
|
|
190
|
+
1. Резолвить `root`, `readdir`, `readFile`, `exists`, `log` з `deps` або дефолтів.
|
|
191
|
+
2. Проходить кожен каталог з `DIRS`.
|
|
192
|
+
3. У кожному каталозі бере файли з розширенням `.md`.
|
|
193
|
+
4. Для кожного `.md`-файла:
|
|
194
|
+
- читає вміст через `readFile`;
|
|
195
|
+
- парсить front-matter через `parseFrontMatter`;
|
|
196
|
+
- якщо парсинг успішний і у front-matter є `id` або `kind` — додає в `artifacts`.
|
|
197
|
+
5. Викликає `analyze(artifacts, resolve)`, де `resolve` — частково застосований `resolveLink(root, file, target, exists)`.
|
|
198
|
+
6. Друкує результат: `JSON.stringify(analysis, null, 2)` для `--json`, інакше `render(analysis)`.
|
|
199
|
+
7. Повертає `1`, якщо існує лінк з `breaking && !ok`, інакше `0`. Нерезолвлений `flow` (info-поле) ігнорується для exit code.
|
|
200
|
+
|
|
201
|
+
## Залежності
|
|
202
|
+
|
|
203
|
+
### Системні (Node.js)
|
|
204
|
+
|
|
205
|
+
- `node:fs` — `existsSync`, `readdirSync`, `readFileSync` (використовуються тільки як дефолтні значення для `deps`).
|
|
206
|
+
- `node:path` — `dirname`, `join` для побудови шляхів у `resolveLink` та CLI.
|
|
207
|
+
- `node:process` — `cwd` (alias-імпорт `processCwd`) для дефолтного кореня.
|
|
208
|
+
|
|
209
|
+
### Внутрішньопроєктні
|
|
210
|
+
|
|
211
|
+
- Жодних. Модуль автономний.
|
|
212
|
+
|
|
213
|
+
### Зовнішні / npm
|
|
214
|
+
|
|
215
|
+
- Жодних.
|
|
216
|
+
|
|
217
|
+
### Споживачі
|
|
218
|
+
|
|
219
|
+
Модуль викликається з диспатчера `n-cursor` як CLI-команда `n-cursor trace`. Експортовані `parseFrontMatter`, `analyze`, `render` доступні для повторного використання іншими інструментами (напр. для дашбордів простежуваності або юніт-тестів).
|
|
220
|
+
|
|
221
|
+
## Потік виконання / Використання
|
|
222
|
+
|
|
223
|
+
### CLI
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
n-cursor trace
|
|
227
|
+
n-cursor trace --json
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Текстовий приклад виводу:**
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
spec · NSPEC-42 [draft]
|
|
234
|
+
→ adr: ../adr/NADR-7.md
|
|
235
|
+
✗ plan: ../plans/NPLAN-99.md (РОЗРИВ — файл відсутній)
|
|
236
|
+
~ flow: ../../.worktrees/feat-x.flow.json (runtime-стан — не рве ланцюг)
|
|
237
|
+
plan · NPLAN-12 [active]
|
|
238
|
+
→ spec: ../specs/NSPEC-42.md
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**JSON-приклад:**
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
[
|
|
245
|
+
{
|
|
246
|
+
"file": "docs/specs/NSPEC-42.md",
|
|
247
|
+
"kind": "spec",
|
|
248
|
+
"id": "NSPEC-42",
|
|
249
|
+
"status": "draft",
|
|
250
|
+
"links": [
|
|
251
|
+
{ "field": "adr", "target": "../adr/NADR-7.md", "ok": true, "breaking": true },
|
|
252
|
+
{ "field": "plan", "target": "../plans/NPLAN-99.md", "ok": false, "breaking": true },
|
|
253
|
+
{ "field": "flow", "target": "../../.worktrees/x.flow.json","ok": false, "breaking": false }
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Алгоритм (псевдо-flowchart)
|
|
260
|
+
|
|
261
|
+
1. **DIR walk** — `for dir in DIRS: for name in readdir(root/dir)`.
|
|
262
|
+
2. **Filter** — лише `*.md`.
|
|
263
|
+
3. **Parse** — `parseFrontMatter(readFile(...))`.
|
|
264
|
+
4. **Filter artifacts** — лише ті, що мають `fm.id` або `fm.kind`.
|
|
265
|
+
5. **Analyze** — для кожного артефакту перетворити front-matter-лінки на `{ field, target, ok, breaking }`.
|
|
266
|
+
6. **Render / JSON** — серіалізація.
|
|
267
|
+
7. **Exit code** — `1` якщо `any(link.breaking && !link.ok)`, інакше `0`.
|
|
268
|
+
|
|
269
|
+
### Програмне використання
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
import { parseFrontMatter, analyze, render, runTraceCli } from './trace.mjs'
|
|
273
|
+
|
|
274
|
+
// Як CLI з мок-FS
|
|
275
|
+
const code = runTraceCli(['--json'], {
|
|
276
|
+
cwd: '/repo',
|
|
277
|
+
readdir: (dir) => fakeFs[dir] ?? [],
|
|
278
|
+
readFile: (file) => fakeFs[file],
|
|
279
|
+
exists: (file) => file in fakeFs,
|
|
280
|
+
log: (msg) => collected.push(msg)
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
// Як бібліотека (без I/O)
|
|
284
|
+
const fm = parseFrontMatter('---\nid: NSPEC-1\nkind: spec\nplan: ../plans/NPLAN-1.md\n---\n# …')
|
|
285
|
+
const result = analyze([{ file: 'docs/specs/x.md', fm }], () => true)
|
|
286
|
+
console.log(render(result))
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Семантика exit code
|
|
290
|
+
|
|
291
|
+
- `0` — всі **chain**-лінки (`adr`, `spec`, `plan`, `change`, `task`) резолвляться.
|
|
292
|
+
- `1` — є хоча б один chain-лінк, що **не** резолвиться. Поле `flow` ніколи не впливає на код виходу.
|
|
293
|
+
|
|
294
|
+
### Тестованість
|
|
295
|
+
|
|
296
|
+
Через повну ін'єкцію `cwd`/`readdir`/`readFile`/`exists`/`log` модуль покривається юніт-тестами без файлової системи. Чисті функції `parseFrontMatter`, `analyze`, `render` тестуються прямо на in-memory даних.
|
|
@@ -77,7 +77,7 @@ export function extractBranchFlag(args) {
|
|
|
77
77
|
export async function runFlowCli(args, deps = {}) {
|
|
78
78
|
const [sub, ...raw] = args
|
|
79
79
|
const handlers = deps.handlers ?? DEFAULT_HANDLERS
|
|
80
|
-
if (!sub || !
|
|
80
|
+
if (!sub || !Object.hasOwn(handlers, sub)) {
|
|
81
81
|
console.error(USAGE)
|
|
82
82
|
return 1
|
|
83
83
|
}
|
|
@@ -15,13 +15,7 @@ import { flowEventsPath } from './events.mjs'
|
|
|
15
15
|
import { executePlan } from './executor.mjs'
|
|
16
16
|
import { generatePlan } from './planner.mjs'
|
|
17
17
|
import { runReview } from './reviewer.mjs'
|
|
18
|
-
import {
|
|
19
|
-
cleanupFlowSiblings,
|
|
20
|
-
flowStatePath,
|
|
21
|
-
readState,
|
|
22
|
-
updateState,
|
|
23
|
-
writeState
|
|
24
|
-
} from './state-store.mjs'
|
|
18
|
+
import { cleanupFlowSiblings, flowStatePath, readState, updateState, writeState } from './state-store.mjs'
|
|
25
19
|
import { createRunner } from './subagent-runner.mjs'
|
|
26
20
|
|
|
27
21
|
/**
|
|
@@ -162,7 +156,9 @@ export async function resume(_rest, deps = {}) {
|
|
|
162
156
|
...s,
|
|
163
157
|
status: 'in_progress',
|
|
164
158
|
plan: s.plan.map(st =>
|
|
165
|
-
st.status === 'done'
|
|
159
|
+
st.status === 'done'
|
|
160
|
+
? st
|
|
161
|
+
: { ...st, retry_count: 0, ...(answers.has(st.step) ? { hint: answers.get(st.step) } : {}) }
|
|
166
162
|
),
|
|
167
163
|
hitl: (s.hitl ?? []).map(q => (q.answer ? { ...q, status: 'answered' } : q))
|
|
168
164
|
}))
|
|
@@ -159,7 +159,7 @@ export async function verify(_rest, deps = {}) {
|
|
|
159
159
|
log(`⚠️ verify: активного flow не визначено — гейти прогнано у ${cwd} без запису стану`)
|
|
160
160
|
}
|
|
161
161
|
// М'які ворота: відсутній план — лише попередження, exit-код визначають gate-и.
|
|
162
|
-
if (state && !
|
|
162
|
+
if (state && !state.plan?.length) {
|
|
163
163
|
log('⚠️ verify: плану не зафіксовано (`flow plan`) — рекомендовано спершу сформувати план')
|
|
164
164
|
}
|
|
165
165
|
|
|
@@ -172,16 +172,12 @@ export async function verify(_rest, deps = {}) {
|
|
|
172
172
|
log(verdict.pass ? '✅ verify: усі gate-и пройдено' : '❌ verify: провалено')
|
|
173
173
|
|
|
174
174
|
if (state) {
|
|
175
|
-
recordTransition(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
fingerprint: verdict.fingerprint,
|
|
182
|
-
status: verdict.pass ? state.status : 'failed'
|
|
183
|
-
})
|
|
184
|
-
)
|
|
175
|
+
recordTransition({ statePath, eventsPath: flowEventsPath(cwd) }, { type: 'verify', pass: verdict.pass }, state => ({
|
|
176
|
+
...state,
|
|
177
|
+
gates: verdict.gates,
|
|
178
|
+
fingerprint: verdict.fingerprint,
|
|
179
|
+
status: verdict.pass ? state.status : 'failed'
|
|
180
|
+
}))
|
|
185
181
|
}
|
|
186
182
|
|
|
187
183
|
return verdict.pass ? 0 : 1
|