@nitra/cursor 3.22.0 → 3.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi-template/extensions/n-cursor-adr/docs/index.md +181 -0
- package/AGENTS.template.md +4 -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 +4 -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-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/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,247 @@
|
|
|
1
|
+
# snapshot.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `snapshot.mjs` — частина шару `lib/` диспетчера flow-задач (`npm/scripts/dispatcher/lib/`). Його єдине призначення — створення та збереження **completion snapshot** (підсумкового слугу про виконання задачі) у durable-сховище, ще до того, як буде видалено transient-файл `.flow.json`.
|
|
6
|
+
|
|
7
|
+
Контекст і причина існування модуля (зі специфікації §3 Ф5, §7):
|
|
8
|
+
|
|
9
|
+
- Стан виконання flow-задачі тимчасово живе у файлі `.flow.json` всередині гілки/робочої теки.
|
|
10
|
+
- На етапі завершення (cleanup) цей transient-стан видаляється.
|
|
11
|
+
- Але потрібно, щоб **слід** про виконану задачу пережив cleanup: для аудиту, історії, ретроспективи.
|
|
12
|
+
- Тому перед видаленням ми будуємо стислий JSON-snapshot і вписуємо його в **task record** — markdown-файл `docs/tasks/<id>.md` між двома HTML-маркерами. Запис **ідемпотентний**: повторний прогін перезаписує блок Summary, а не дублює його.
|
|
13
|
+
|
|
14
|
+
Модуль експонує три pure/IO-функції з чітко розділеними рівнями (чиста трансформація → чистий upsert у тексті → IO у файлову систему), що робить логіку легко тестованою без mock-ів FS на більшості шляхів.
|
|
15
|
+
|
|
16
|
+
## Експорти / API
|
|
17
|
+
|
|
18
|
+
Модуль експортує три іменовані функції (`export function ...`):
|
|
19
|
+
|
|
20
|
+
| Експорт | Тип | Призначення |
|
|
21
|
+
| ---------------------------------------------- | ------------ | -------------------------------------------------------------------------------------------- |
|
|
22
|
+
| `buildCompletionSnapshot(state, now?)` | pure-функція | Складає JSON-об'єкт snapshot зі стану `.flow.json`. |
|
|
23
|
+
| `upsertSummaryBlock(content, snapshot)` | pure-функція | Вставляє або оновлює блок Summary у наданому markdown-тексті між маркерами. |
|
|
24
|
+
| `writeSummaryToTaskRecord(taskPath, snapshot)` | IO-функція | Читає файл task record (якщо існує), застосовує `upsertSummaryBlock` і пише результат назад. |
|
|
25
|
+
|
|
26
|
+
Внутрішні (не експортовані) константи:
|
|
27
|
+
|
|
28
|
+
- `SUMMARY_START = '<!-- flow:summary:start -->'` — відкриваючий HTML-коментар-маркер.
|
|
29
|
+
- `SUMMARY_END = '<!-- flow:summary:end -->'` — закриваючий HTML-коментар-маркер.
|
|
30
|
+
|
|
31
|
+
Ці маркери — публічний контракт формату task record: будь-який інший інструмент може шукати ці ж літерали, щоб витягнути або переписати блок.
|
|
32
|
+
|
|
33
|
+
## Функції
|
|
34
|
+
|
|
35
|
+
### `buildCompletionSnapshot(state, now = Date.now)`
|
|
36
|
+
|
|
37
|
+
**Сигнатура:**
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
buildCompletionSnapshot(state: object, now?: () => number): object
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Параметри:**
|
|
44
|
+
|
|
45
|
+
- `state` — об'єкт стану задачі (типово розпарсений `.flow.json`). Очікувані поля (всі опційні, мають дефолти):
|
|
46
|
+
- `state.status` — рядковий статус, дефолт `'done'`.
|
|
47
|
+
- `state.branch` — назва гілки, дефолт `null`.
|
|
48
|
+
- `state.metadata?.base_commit` — base commit гілки; якщо відсутній, fallback `state.base_commit`; дефолт `null`.
|
|
49
|
+
- `state.gates` — масив об'єктів `{ name, ok }`; кожен gate перетворюється у пару `[name, ok ? 'ok' : 'fail']`.
|
|
50
|
+
- `state.change` — інформація про change-файл (n-changelog), дефолт `null`.
|
|
51
|
+
- `state.notified` — статус нотифікації, дефолт `null`.
|
|
52
|
+
- `now` — фабрика часу: функція, що повертає мілісекунди (`number`). За замовчуванням — `Date.now`. Передається явно, щоб **тести могли інжектувати детерміністичний час**.
|
|
53
|
+
|
|
54
|
+
**Повертає:**
|
|
55
|
+
|
|
56
|
+
Об'єкт `snapshot` із полями:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
{
|
|
60
|
+
status: string, // state.status ?? 'done'
|
|
61
|
+
branch: string|null,
|
|
62
|
+
base_commit: string|null,
|
|
63
|
+
gates: Record<string,'ok'|'fail'>, // плоска мапа name → status
|
|
64
|
+
change: any|null,
|
|
65
|
+
notified: any|null,
|
|
66
|
+
finished_at: string // ISO-8601, отриманий через new Date(now()).toISOString()
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Side effects:** жодних. Це чиста функція (за умови чистоти переданої `now`).
|
|
71
|
+
|
|
72
|
+
**Особливості реалізації:**
|
|
73
|
+
|
|
74
|
+
- Поле `base_commit` має **двоступеневий fallback**: спочатку `state.metadata?.base_commit`, потім `state.base_commit`. Це покриває обидва формати state, що зустрічаються в практиці.
|
|
75
|
+
- `gates` нормалізуються до **рядкового** статусу `'ok'`/`'fail'` (а не булевого `ok`), щоб JSON у markdown був самодокументованим і людиночитним.
|
|
76
|
+
- `finished_at` обчислюється з результату виклику `now()`, тому час фіксується саме в момент побудови snapshot.
|
|
77
|
+
- Якщо `state.gates` відсутній — використовується порожній масив, і поле `gates` буде `{}`.
|
|
78
|
+
|
|
79
|
+
### `upsertSummaryBlock(content, snapshot)`
|
|
80
|
+
|
|
81
|
+
**Сигнатура:**
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
upsertSummaryBlock(content: string, snapshot: object): string
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Параметри:**
|
|
88
|
+
|
|
89
|
+
- `content` — вихідний markdown-текст task record (може бути порожнім рядком).
|
|
90
|
+
- `snapshot` — об'єкт, що буде серіалізований у JSON (через `JSON.stringify(..., null, 2)`) усередину блоку.
|
|
91
|
+
|
|
92
|
+
**Повертає:** новий markdown-рядок зі вставленим або оновленим блоком Summary.
|
|
93
|
+
|
|
94
|
+
**Side effects:** жодних, чиста рядкова трансформація.
|
|
95
|
+
|
|
96
|
+
**Структура блоку, який будує функція:**
|
|
97
|
+
|
|
98
|
+
````text
|
|
99
|
+
<!-- flow:summary:start -->
|
|
100
|
+
## Summary
|
|
101
|
+
```json
|
|
102
|
+
{ ... pretty-printed snapshot ... }
|
|
103
|
+
````
|
|
104
|
+
|
|
105
|
+
<!-- flow:summary:end -->
|
|
106
|
+
|
|
107
|
+
````
|
|
108
|
+
|
|
109
|
+
(У реальному виводі трійні бектики справжні; тут показано схематично, бо ми всередині markdown.)
|
|
110
|
+
|
|
111
|
+
**Алгоритм (idempotent upsert):**
|
|
112
|
+
|
|
113
|
+
1. Сформувати рядок `block` із маркерів, заголовка `## Summary`, fenced JSON-блоку та закриваючого маркера.
|
|
114
|
+
2. Знайти індекси `i = content.indexOf(SUMMARY_START)` та `j = content.indexOf(SUMMARY_END)`.
|
|
115
|
+
3. Якщо обидва маркери знайдено і `j > i` — **замінити** діапазон `[i, j + len(SUMMARY_END))` на `block`. Це гарантує, що повторний виклик не дублює блок і не залишає старого вмісту.
|
|
116
|
+
4. Інакше (маркерів немає, або вони у неправильному порядку) — **дописати** блок у кінець: `content.trimEnd() + '\n\n' + block + '\n'`. `trimEnd()` прибирає лишні хвостові переноси, потім додається порожній рядок-розділювач.
|
|
117
|
+
|
|
118
|
+
**Кейси (контракт):**
|
|
119
|
+
|
|
120
|
+
| Вхідний `content` | Поведінка |
|
|
121
|
+
|---|---|
|
|
122
|
+
| Порожній рядок `''` | Дописує `\n\n<block>\n` (фактично починається з `\n\n` через `trimEnd('')`). |
|
|
123
|
+
| Markdown без маркерів | Дописує блок у кінець із розділювачем. |
|
|
124
|
+
| Markdown із валідною парою маркерів | Замінює вміст між ними (включно з самими маркерами) на свіжий блок. |
|
|
125
|
+
| Markdown із поодиноким `SUMMARY_START` без `SUMMARY_END` | Йде у гілку append (дописати в кінець). |
|
|
126
|
+
| Markdown із `SUMMARY_END` перед `SUMMARY_START` (`j <= i`) | Теж йде в append. |
|
|
127
|
+
|
|
128
|
+
### `writeSummaryToTaskRecord(taskPath, snapshot)`
|
|
129
|
+
|
|
130
|
+
**Сигнатура:**
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
writeSummaryToTaskRecord(taskPath: string, snapshot: object): void
|
|
134
|
+
````
|
|
135
|
+
|
|
136
|
+
**Параметри:**
|
|
137
|
+
|
|
138
|
+
- `taskPath` — **абсолютний** шлях до task record-файла (типово `<repo>/docs/tasks/<id>.md`).
|
|
139
|
+
- `snapshot` — об'єкт snapshot (зазвичай результат `buildCompletionSnapshot`).
|
|
140
|
+
|
|
141
|
+
**Повертає:** `void`.
|
|
142
|
+
|
|
143
|
+
**Side effects:**
|
|
144
|
+
|
|
145
|
+
- Кидає `Error`, якщо `taskPath` **не** абсолютний (`isAbsolute(taskPath) === false`). Текст помилки: `writeSummaryToTaskRecord: очікується абсолютний шлях (отримано: <taskPath>)`.
|
|
146
|
+
- Якщо файл існує — читає його синхронно (`readFileSync(taskPath, 'utf8')`).
|
|
147
|
+
- Якщо файл не існує — використовує порожній рядок як base.
|
|
148
|
+
- Записує результат `upsertSummaryBlock(...)` у `taskPath` синхронно (`writeFileSync(..., 'utf8')`). Файл буде створено, якщо його не було.
|
|
149
|
+
|
|
150
|
+
**Особливості:**
|
|
151
|
+
|
|
152
|
+
- Усі IO — **синхронні**. Це свідомий вибір: функція викликається на cleanup-етапі диспетчера, де простота й детерміністичність важливіші за throughput.
|
|
153
|
+
- Не створює проміжних каталогів. Очікується, що `docs/tasks/` уже існує (інакше `writeFileSync` кине `ENOENT`).
|
|
154
|
+
- Не виконує бекапу: попередній блок Summary перезаписується новим (про що дбає `upsertSummaryBlock`).
|
|
155
|
+
|
|
156
|
+
## Залежності
|
|
157
|
+
|
|
158
|
+
**Зовнішні (Node.js core, через `node:` префікс):**
|
|
159
|
+
|
|
160
|
+
- `node:fs` — `existsSync`, `readFileSync`, `writeFileSync` (синхронний IO).
|
|
161
|
+
- `node:path` — `isAbsolute` (валідація вхідного шляху).
|
|
162
|
+
|
|
163
|
+
**Внутрішніх залежностей** (на інші модулі диспетчера) — **немає**. Модуль самодостатній і не імпортує нічого з `lib/` чи проєкту.
|
|
164
|
+
|
|
165
|
+
**Зовнішні npm-пакети:** відсутні.
|
|
166
|
+
|
|
167
|
+
**Тип/середовище:** ESM (`.mjs`, `import`-синтаксис), запускається у Node.js / Bun.
|
|
168
|
+
|
|
169
|
+
## Потік виконання / Використання
|
|
170
|
+
|
|
171
|
+
### Сценарій 1: завершення flow-задачі (основний use case)
|
|
172
|
+
|
|
173
|
+
Псевдокод callsite (типового місця виклику в диспетчері):
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
177
|
+
import { join } from 'node:path'
|
|
178
|
+
import { buildCompletionSnapshot, writeSummaryToTaskRecord } from './lib/snapshot.mjs'
|
|
179
|
+
|
|
180
|
+
// 1. Зчитати transient-стан.
|
|
181
|
+
const state = JSON.parse(readFileSync('.flow.json', 'utf8'))
|
|
182
|
+
|
|
183
|
+
// 2. Побудувати completion snapshot.
|
|
184
|
+
const snapshot = buildCompletionSnapshot(state)
|
|
185
|
+
|
|
186
|
+
// 3. Уписати його в durable task record.
|
|
187
|
+
const taskPath = join(repoRoot, 'docs', 'tasks', `${state.id}.md`)
|
|
188
|
+
writeSummaryToTaskRecord(taskPath, snapshot)
|
|
189
|
+
|
|
190
|
+
// 4. Тепер безпечно видаляти `.flow.json` (cleanup).
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Сценарій 2: тестування (інжекція часу)
|
|
194
|
+
|
|
195
|
+
`buildCompletionSnapshot` приймає `now` як параметр, тож тест може зафіксувати `finished_at`:
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
const fixedNow = () => 1_700_000_000_000
|
|
199
|
+
const snap = buildCompletionSnapshot(state, fixedNow)
|
|
200
|
+
// snap.finished_at === '2023-11-14T22:13:20.000Z'
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Сценарій 3: ідемпотентний повторний запис
|
|
204
|
+
|
|
205
|
+
Виклик `writeSummaryToTaskRecord` на тому ж шляху з тим самим snapshot **не** змінює файл змістовно (тільки переписує блок Summary тим самим вмістом). На різних snapshot — оновлює блок без дублювання та без впливу на решту markdown поза маркерами.
|
|
206
|
+
|
|
207
|
+
### Потік даних
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
.flow.json (transient)
|
|
211
|
+
│
|
|
212
|
+
▼ JSON.parse
|
|
213
|
+
state object
|
|
214
|
+
│
|
|
215
|
+
▼ buildCompletionSnapshot(state, now)
|
|
216
|
+
snapshot object ─────────────┐
|
|
217
|
+
▼ upsertSummaryBlock(content, snapshot)
|
|
218
|
+
docs/tasks/<id>.md (existing) ───┤
|
|
219
|
+
│ ▼
|
|
220
|
+
│ updated markdown
|
|
221
|
+
│ │
|
|
222
|
+
└─◀────── writeFileSync ──┘
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Інваріанти
|
|
226
|
+
|
|
227
|
+
- `finished_at` завжди є валідним ISO-8601 рядком.
|
|
228
|
+
- `gates` завжди є об'єктом (нехай і порожнім), ніколи не `undefined`.
|
|
229
|
+
- Файл task record після виклику завжди містить рівно один блок Summary між маркерами.
|
|
230
|
+
- HTML-маркери залишаються в markdown навмисно: вони не рендеряться у GitHub/MD-рендерерах, але слугують machine-readable якорями для idempotent upsert.
|
|
231
|
+
|
|
232
|
+
## Rebuild Test
|
|
233
|
+
|
|
234
|
+
Mental-rebuild перевірка (чи документація достатня для відтворення модуля з нуля без перегляду коду):
|
|
235
|
+
|
|
236
|
+
1. **Призначення зрозуміле?** Так — будувати completion snapshot і вписувати його між HTML-маркерами в `docs/tasks/<id>.md` перед cleanup `.flow.json`.
|
|
237
|
+
2. **Експорти й сигнатури повні?** Так — три функції з типами параметрів, дефолтами, типами повернення.
|
|
238
|
+
3. **Формат snapshot задокументовано?** Так — перелік полів, дефолти, fallback-логіка для `base_commit`, нормалізація `gates` до `'ok'`/`'fail'`.
|
|
239
|
+
4. **Формат блоку в markdown задокументовано?** Так — маркери, `## Summary`, fenced JSON-блок, pretty-print 2 пробіли.
|
|
240
|
+
5. **Алгоритм upsert описаний?** Так — пошук маркерів, заміна діапазону при валідній парі, інакше append із `trimEnd` + `\n\n`.
|
|
241
|
+
6. **Обробка помилок описана?** Так — `Error` на не-абсолютний шлях, відсутність файлу = порожній base.
|
|
242
|
+
7. **Залежності перелічено?** Так — `node:fs` (3 функції), `node:path` (`isAbsolute`).
|
|
243
|
+
8. **Side effects явні?** Так — синхронний IO лише в `writeSummaryToTaskRecord`; решта pure.
|
|
244
|
+
9. **Інваріанти й кейси edge?** Так — порожній content, поодинокий маркер, зворотний порядок маркерів, повторний запис.
|
|
245
|
+
10. **Інжекція часу для тестів?** Так — параметр `now`.
|
|
246
|
+
|
|
247
|
+
Рекомпіляція з документації → ідентичний за поведінкою модуль: можлива.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# spec.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль реалізує CLI-підкоманду `flow spec [--panel] [<spec.md>]` — фазу дизайну в lifecycle Пасивного Турнікета (§3 правила `flow.mdc`). Її призначення — зафіксувати у файлі стану воркфлоу (`flow-state`) шлях до spec-документа (`docs/specs/<date>-<slug>.md`), отриманого внаслідок brainstorm-сесії, та виконати read-only верифікацію ланцюга трасування (`trace`) між артефактами ADR → spec → plan.
|
|
6
|
+
|
|
7
|
+
Ключові властивості:
|
|
8
|
+
|
|
9
|
+
- **Код не пишеться:** модуль лише оновлює state-store та логує події, лінки front-matter (`adr/spec/plan`) у самому документі формує агент за контрактом `flow.mdc`.
|
|
10
|
+
- **Brainstorm two-track:** human↔agent відбувається у звичайному IDE-діалозі; agent↔agent — через прапор `--panel`, який запускає синтез персон та суддю через `runPanel`.
|
|
11
|
+
- **Risk-driven review:** значення `risk` із front-matter spec-документа (`low | med | high`) перетікає у стан, керуючи глибиною подальшої фази `flow review`.
|
|
12
|
+
- **Worktree-aware:** якщо `cwd` поза worktree, активний flow автоматично резолвиться через `resolveActiveFlowState`.
|
|
13
|
+
|
|
14
|
+
Модуль є чистою функцією верхнього рівня з контрольованими ін'єкціями залежностей (`deps`), що робить його придатним для unit-тестування без файлової системи реального проєкту.
|
|
15
|
+
|
|
16
|
+
## Експорти / API
|
|
17
|
+
|
|
18
|
+
| Експорт | Тип | Призначення |
|
|
19
|
+
| ------------------- | ---------------- | ---------------------------------------------------------------------------------------- |
|
|
20
|
+
| `spec(rest, deps?)` | `async function` | Точка входу CLI-підкоманди `flow spec`. Повертає exit code (`0` — успіх, `1` — помилка). |
|
|
21
|
+
|
|
22
|
+
Внутрішні (не експортуються):
|
|
23
|
+
|
|
24
|
+
| Ідентифікатор | Тип | Призначення |
|
|
25
|
+
| ---------------------------- | ------------- | ------------------------------------------------------------------------- |
|
|
26
|
+
| `RISKS` | `Set<string>` | Допустимі рівні ризику: `low`, `med`, `high`. |
|
|
27
|
+
| `riskFromSpec(doc, current)` | `function` | Зчитує валідний `risk` зі spec-frontmatter або повертає поточний у стані. |
|
|
28
|
+
|
|
29
|
+
## Функції
|
|
30
|
+
|
|
31
|
+
### `RISKS`
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
const RISKS = new Set(['low', 'med', 'high'])
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Замкнений перелік допустимих значень поля `risk` у front-matter spec-документа. Будь-яке інше значення (включно з `undefined`, опискою, або відсутністю фронт-матеру) ігнорується — у такому разі ризик у стані не змінюється.
|
|
38
|
+
|
|
39
|
+
### `riskFromSpec(doc, current)`
|
|
40
|
+
|
|
41
|
+
**Сигнатура:**
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
function riskFromSpec(doc: string, current: string | undefined): string | undefined
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Параметри:**
|
|
48
|
+
|
|
49
|
+
- `doc` (`string`) — абсолютний або відносний шлях до spec-документа (`*.md`).
|
|
50
|
+
- `current` (`string | undefined`) — поточне значення `risk` із state-store; використовується як fallback, коли в документі не вказано валідного ризику.
|
|
51
|
+
|
|
52
|
+
**Повертає:** `string | undefined` — рівень ризику (`low`, `med`, `high`) або поточне значення.
|
|
53
|
+
|
|
54
|
+
**Семантика:**
|
|
55
|
+
|
|
56
|
+
1. Зчитує вміст файлу `doc` як UTF-8 рядок.
|
|
57
|
+
2. Парсить front-matter через `parseFrontMatter`.
|
|
58
|
+
3. Якщо у фронт-матері є поле `risk` і воно є членом `RISKS` — повертає його.
|
|
59
|
+
4. Інакше повертає `current` (без модифікації).
|
|
60
|
+
|
|
61
|
+
**Side effects:**
|
|
62
|
+
|
|
63
|
+
- Виконує синхронний read файлової системи (`readFileSync`).
|
|
64
|
+
- Будь-який виняток (відсутній файл, помилка парсингу, прав доступу) поглинається `try/catch` і функція повертає `current` — це гарантує, що відсутність front-matter не блокує транзицію стану.
|
|
65
|
+
|
|
66
|
+
### `spec(rest, deps)`
|
|
67
|
+
|
|
68
|
+
**Сигнатура:**
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
async function spec(
|
|
72
|
+
rest: string[],
|
|
73
|
+
deps?: {
|
|
74
|
+
cwd?: string,
|
|
75
|
+
branch?: string,
|
|
76
|
+
log?: (m: string) => void,
|
|
77
|
+
runner?: object,
|
|
78
|
+
trace?: (cwd: string) => number,
|
|
79
|
+
now?: () => number
|
|
80
|
+
}
|
|
81
|
+
): Promise<number>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Параметри:**
|
|
85
|
+
|
|
86
|
+
- `rest` (`string[]`) — позиційні аргументи CLI після підкоманди `spec`. Розпізнаються:
|
|
87
|
+
- `--panel` — флаг увімкнення agent-panel brainstorm.
|
|
88
|
+
- Будь-який аргумент, що закінчується на `.md` — явний шлях до spec-документа (інакше використовується `resolveArtifact`).
|
|
89
|
+
- `deps` (`object`, опційно) — ін'єкції для тестування та advanced use cases:
|
|
90
|
+
- `cwd` — стартовий робочий каталог (default: `process.cwd()`).
|
|
91
|
+
- `branch` — гілка для резолву активного flow (передається у `resolveActiveFlowState`).
|
|
92
|
+
- `log` — функція логування (default: `console.error`).
|
|
93
|
+
- `runner` — попередньо створений subagent runner (інакше викликається `createRunner(deps)`).
|
|
94
|
+
- `trace` — функція трасування для `verifyTrace`.
|
|
95
|
+
- `now` — джерело часу для `recordTransition` (default: `Date.now`).
|
|
96
|
+
|
|
97
|
+
**Повертає:** `Promise<number>` — exit code:
|
|
98
|
+
|
|
99
|
+
- `0` — транзиція стану успішно записана.
|
|
100
|
+
- `1` — помилка резолву стану / відсутній state / помилка створення runner / відсутній spec-документ.
|
|
101
|
+
|
|
102
|
+
**Потік виконання:**
|
|
103
|
+
|
|
104
|
+
1. **Резолв активного flow.** Викликає `resolveActiveFlowState({ cwd, branch }, deps)`. Якщо `statePath` не визначено — логує помилку й повертає `1`. Якщо flow авторезолвлено (cwd поза worktree) — логує інформаційне повідомлення з міткою.
|
|
105
|
+
2. **Читання стану.** `readState(statePath)` повертає об'єкт стану або `null`. У разі `null` — логує підказку `flow init` і повертає `1`.
|
|
106
|
+
3. **Опційний panel-brainstorm.** Якщо `rest` містить `--panel`:
|
|
107
|
+
- Створює runner через `createRunner(deps)` (якщо не передано в `deps.runner`); виняток → лог + `1`.
|
|
108
|
+
- Викликає `runPanel({ task: state.branch, cwd, runner, log, mode: 'spec' })`.
|
|
109
|
+
- Якщо повернувся синтез — логує його (з підказкою зберегти в `docs/specs/` і повторити `flow spec`). Об'єктний синтез серіалізується через `JSON.stringify`.
|
|
110
|
+
- **Важливо:** після `--panel` функція не виходить — продовжує спробу резолву документа (наступний крок). Це дозволяє за один виклик і синтезувати, і зафіксувати.
|
|
111
|
+
4. **Резолв документа.** Шукає в `rest` перший аргумент, що закінчується на `.md`; інакше — `resolveArtifact(cwd, 'specs', state.branch)`. Якщо документ не знайдено або файл не існує — логує підказку про brainstorm і повертає `1`.
|
|
112
|
+
5. **Trace-верифікація.** `verifyTrace(cwd, deps.trace)` — read-only перевірка ланцюга front-matter (adr/spec/plan). Якщо ланцюг розірвано — лише warning у лог (не фатально).
|
|
113
|
+
6. **Обчислення risk.** `risk = riskFromSpec(doc, state.risk)` — front-matter spec має пріоритет над поточним.
|
|
114
|
+
7. **Запис транзиції.** `recordTransition` із параметрами:
|
|
115
|
+
- `paths`: `{ statePath, eventsPath: flowEventsPath(cwd) }`.
|
|
116
|
+
- `event`: `{ type: 'spec' }`.
|
|
117
|
+
- `mutator`: `s => ({ ...s, spec_doc: doc, risk, status: 'spec' })`.
|
|
118
|
+
- `clock`: `deps.now ?? Date.now`.
|
|
119
|
+
8. **Завершення.** Лог `spec: зафіксовано <doc> → status: spec (risk <risk|—>)` і повернення `0`.
|
|
120
|
+
|
|
121
|
+
**Side effects:**
|
|
122
|
+
|
|
123
|
+
- Логування у stderr (`console.error` або кастомний `log`).
|
|
124
|
+
- Read-only доступ до файлової системи (`existsSync`, `readFileSync` у `riskFromSpec`).
|
|
125
|
+
- Запис у state-store та events-log через `recordTransition`.
|
|
126
|
+
- Можливий запуск subagent-runner (panel mode), який сам має побічні ефекти (мережа/IPC).
|
|
127
|
+
|
|
128
|
+
## Залежності
|
|
129
|
+
|
|
130
|
+
### Стандартна бібліотека Node.js
|
|
131
|
+
|
|
132
|
+
| Імпорт | Звідки | Використання |
|
|
133
|
+
| ------------------- | -------------- | --------------------------------------------------------------------- |
|
|
134
|
+
| `existsSync` | `node:fs` | Перевірка існування spec-документа перед записом транзиції. |
|
|
135
|
+
| `readFileSync` | `node:fs` | Зчитування spec-документа для парсингу front-matter у `riskFromSpec`. |
|
|
136
|
+
| `cwd as processCwd` | `node:process` | Default-значення для `deps.cwd`. |
|
|
137
|
+
|
|
138
|
+
### Внутрішні модулі
|
|
139
|
+
|
|
140
|
+
| Імпорт | Шлях | Призначення |
|
|
141
|
+
| -------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
142
|
+
| `resolveArtifact`, `verifyTrace` | `./artifact.mjs` | Резолв шляху артефакту за конвенцією `docs/<kind>/<date>-<slug>.md` та верифікація trace-ланцюга front-matter. |
|
|
143
|
+
| `flowEventsPath` | `./events.mjs` | Шлях до файлу подій воркфлоу (для аудиту транзицій). |
|
|
144
|
+
| `runPanel` | `./plan-panel.mjs` | Запуск agent-panel brainstorm у режимі `mode: 'spec'`. |
|
|
145
|
+
| `createRunner` | `./subagent-runner.mjs` | Фабрика subagent runner для panel mode. |
|
|
146
|
+
| `readState`, `recordTransition` | `./state-store.mjs` | Читання поточного стану та атомарний запис транзиції з мутатором. |
|
|
147
|
+
| `resolveActiveFlowState` | `./flow-resolve.mjs` | Авторезолв активного flow (worktree-awareness, branch fallback). |
|
|
148
|
+
| `parseFrontMatter` | `../trace.mjs` | Парсинг YAML-подібного front-matter Markdown-документа. |
|
|
149
|
+
|
|
150
|
+
## Потік виконання / Використання
|
|
151
|
+
|
|
152
|
+
### CLI-сценарій
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# 1. Простий запис: spec-документ резолвиться за конвенцією branch → docs/specs/
|
|
156
|
+
flow spec
|
|
157
|
+
|
|
158
|
+
# 2. Явний шлях до документа
|
|
159
|
+
flow spec docs/specs/2026-01-15-new-feature.md
|
|
160
|
+
|
|
161
|
+
# 3. З agent-panel brainstorm (синтез персон, потім збереження документа вручну)
|
|
162
|
+
flow spec --panel
|
|
163
|
+
|
|
164
|
+
# 4. Комбіноване: panel + явний документ
|
|
165
|
+
flow spec --panel docs/specs/2026-01-15-new-feature.md
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Граф станів
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
flow init → status: init
|
|
172
|
+
↓
|
|
173
|
+
(brainstorm в IDE або через --panel)
|
|
174
|
+
↓
|
|
175
|
+
flow spec → status: spec, spec_doc=<path>, risk=<low|med|high>
|
|
176
|
+
↓
|
|
177
|
+
flow plan / flow review / ...
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Інваріанти
|
|
181
|
+
|
|
182
|
+
- Перед `flow spec` обов'язково має бути виконано `flow init` (інакше — exit 1).
|
|
183
|
+
- Spec-документ має існувати на ФС (`existsSync`).
|
|
184
|
+
- Trace-розрив **не** блокує транзицію — лише warning (бо лінки пише агент і може не встигнути на момент запуску).
|
|
185
|
+
- Panel-mode не перериває основний flow: навіть якщо синтез повернувся, функція продовжує спробу резолвити документ. Якщо документу ще нема — exit 1 з підказкою зберегти результат.
|
|
186
|
+
|
|
187
|
+
### Контракт із `flow.mdc`
|
|
188
|
+
|
|
189
|
+
- Контракт front-matter (поля `adr`, `spec`, `plan`, `risk`) — відповідальність агента, що генерує/редагує Markdown-документ.
|
|
190
|
+
- `risk` із spec має пріоритет над попереднім `state.risk` — це механізм downstream-керування глибиною review.
|
|
191
|
+
- Подія `{ type: 'spec' }` фіксується у `events.jsonl` для подальшого аудиту lifecycle.
|
|
192
|
+
|
|
193
|
+
### Тестованість
|
|
194
|
+
|
|
195
|
+
Усі зовнішні точки контакту проброшено через `deps`:
|
|
196
|
+
|
|
197
|
+
- `cwd`, `branch` — детермінований резолв flow.
|
|
198
|
+
- `log` — capture логів у тестах.
|
|
199
|
+
- `runner` — мок subagent без реальних викликів LLM.
|
|
200
|
+
- `trace` — мок trace-перевірки.
|
|
201
|
+
- `now` — детерміновані timestamps у events.
|
|
202
|
+
|
|
203
|
+
Це дозволяє покрити модуль unit-тестами без файлової системи (тільки `readFileSync`/`existsSync` потребують фейкових файлів або моків `node:fs`).
|