@nitra/cursor 3.22.0 → 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 +31 -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-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,348 @@
|
|
|
1
|
+
# active.mjs — Активний Раннер flow-диспетчера
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `active.mjs` реалізує **Активний Раннер** диспетчера потоків (Фасад B, spec §8.1) — повний 5-фазний життєвий цикл автоматизованого виконання задачі в ізольованому git-worktree. Він зшиває чотири підсистеми в одну CLI-команду:
|
|
6
|
+
|
|
7
|
+
1. `ensureWorktree` — підготовка ізольованого worktree під цільову гілку;
|
|
8
|
+
2. `planner.generatePlan` — побудова покрокового плану через LLM-runner;
|
|
9
|
+
3. `executor.executePlan` — послідовне виконання кроків з verify/commit;
|
|
10
|
+
4. `reviewer.runReview` — gate-перевірки (verify) після кожного кроку.
|
|
11
|
+
|
|
12
|
+
Усі IO-залежності (`runner`, `verify`, `commit`, `run`, `now`, `log`) **ін'єктуються** через об'єкт `deps`, тож модуль повністю тестується без реальних LLM, git-операцій чи gate-команд. У автономному режимі (`--autonomous`) runner додатково обгортається `withBudget` (§9.4) для жорсткого обмеження API-викликів і вартості.
|
|
13
|
+
|
|
14
|
+
Експортується чотири CLI-обробники:
|
|
15
|
+
|
|
16
|
+
- `run` — повний цикл build (ensureWorktree → план → executor);
|
|
17
|
+
- `resume` — продовження з чекпойнта зі скиданням часткового доробку (safe-resume, §4.1.7);
|
|
18
|
+
- `cancel` — прибирання transient sibling-файлів стану;
|
|
19
|
+
- `repair` — діагностика стану або жорстке скидання робочого дерева до HEAD.
|
|
20
|
+
|
|
21
|
+
Файл стану (`flow.json`), журнал подій (`flow.events.jsonl`) та lock-файли розташовуються поруч із worktree-кореневою текою; шляхи до них формують `flowStatePath` / `flowEventsPath` зі `state-store.mjs` та `events.mjs`.
|
|
22
|
+
|
|
23
|
+
## Експорти / API
|
|
24
|
+
|
|
25
|
+
| Експорт | Тип | Призначення |
|
|
26
|
+
| ---------------------- | ---------------- | ----------------------------------------------------------------- |
|
|
27
|
+
| `run(rest, deps?)` | `async function` | Команда `flow run` — повний цикл build (планування + виконання). |
|
|
28
|
+
| `resume(_rest, deps?)` | `async function` | Команда `flow resume` — продовжити з останнього чекпойнта. |
|
|
29
|
+
| `cancel(_rest, deps?)` | `async function` | Команда `flow cancel` — прибрати транзитні sibling-и стану. |
|
|
30
|
+
| `repair(rest, deps?)` | `async function` | Команда `flow repair [--discard-step-work]` — fail-closed escape. |
|
|
31
|
+
|
|
32
|
+
Усі експорти повертають `Promise<number>` — exit code процесу:
|
|
33
|
+
|
|
34
|
+
- `0` — успіх (`done` або no-op);
|
|
35
|
+
- `1` — fail (помилка, нема стану/плану, бюджет вичерпано, стан пошкоджено);
|
|
36
|
+
- `2` — `blocked-on-human` (потрібне HITL-втручання).
|
|
37
|
+
|
|
38
|
+
### Внутрішні (не експортовані) helper-и
|
|
39
|
+
|
|
40
|
+
| Ім'я | Призначення |
|
|
41
|
+
| ------------------------- | ------------------------------------------------------------------------------------ |
|
|
42
|
+
| `defaultCommit(cwd, msg)` | Дефолтний commit-стратег: `git add -A && git commit -m <msg>` у worktree. |
|
|
43
|
+
| `defaultVerify(cwd)` | Дефолтний verify: проганяє `runReview` з реальним `run` і `fingerprint: () => null`. |
|
|
44
|
+
| `readFlowAutonomous(cwd)` | Зчитує секцію `flow.autonomous` з `.n-cursor.json` (бюджет автономки). |
|
|
45
|
+
|
|
46
|
+
## Функції
|
|
47
|
+
|
|
48
|
+
### `defaultCommit(cwd, msg)`
|
|
49
|
+
|
|
50
|
+
**Сигнатура:** `function defaultCommit(cwd: string, msg: string): void`
|
|
51
|
+
|
|
52
|
+
**Параметри:**
|
|
53
|
+
|
|
54
|
+
- `cwd` — абсолютний шлях до worktree;
|
|
55
|
+
- `msg` — повідомлення коміту.
|
|
56
|
+
|
|
57
|
+
**Повертає:** `void`.
|
|
58
|
+
|
|
59
|
+
**Side effects:** виконує два синхронних `spawnSync('git', …)`-виклики у вказаному `cwd`:
|
|
60
|
+
|
|
61
|
+
1. `git add -A` — індексує всі зміни;
|
|
62
|
+
2. `git commit -m <msg>` — створює коміт.
|
|
63
|
+
|
|
64
|
+
Помилки git **не пробрасуються** — exit code spawnSync ігнорується (виклик «оптимістичний»).
|
|
65
|
+
|
|
66
|
+
### `defaultVerify(cwd)`
|
|
67
|
+
|
|
68
|
+
**Сигнатура:** `function defaultVerify(cwd: string): { pass: boolean, failedOutput: string | null }`
|
|
69
|
+
|
|
70
|
+
**Параметри:**
|
|
71
|
+
|
|
72
|
+
- `cwd` — корінь worktree, де крутитимуться gate-команди.
|
|
73
|
+
|
|
74
|
+
**Повертає:** verdict від `runReview` — об'єкт із полями `pass: boolean` та `failedOutput: string | null`.
|
|
75
|
+
|
|
76
|
+
**Side effects:** делегує `runReview` зі `./reviewer.mjs`, передаючи реальний `run = realRun` (синхронний spawn зі `./commands.mjs`) та `fingerprint: () => null` (порівняння за хешем артефактів вимкнено).
|
|
77
|
+
|
|
78
|
+
### `readFlowAutonomous(cwd)`
|
|
79
|
+
|
|
80
|
+
**Сигнатура:** `function readFlowAutonomous(cwd: string): { maxApiCalls?: number, maxCostUsd?: number, onBudgetExceeded?: string }`
|
|
81
|
+
|
|
82
|
+
**Параметри:**
|
|
83
|
+
|
|
84
|
+
- `cwd` — корінь проєкту, де лежить `.n-cursor.json`.
|
|
85
|
+
|
|
86
|
+
**Повертає:** об'єкт із секції `flow.autonomous` конфігу або порожній `{}`, якщо файл відсутній/невалідний.
|
|
87
|
+
|
|
88
|
+
**Side effects:** **синхронно** читає `<cwd>/.n-cursor.json` через `readFileSync`. Будь-яка помилка (відсутній файл, невалідний JSON) глушиться `try/catch` → повертається `{}`.
|
|
89
|
+
|
|
90
|
+
### `run(rest, deps?)`
|
|
91
|
+
|
|
92
|
+
**Сигнатура:**
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
async function run(
|
|
96
|
+
rest: string[],
|
|
97
|
+
deps?: {
|
|
98
|
+
runner?: object,
|
|
99
|
+
verify?: (cwd: string) => { pass: boolean, failedOutput: string | null },
|
|
100
|
+
commit?: (cwd: string, msg: string) => void,
|
|
101
|
+
run?: (cmd: string, args: string[], opts: object) => object,
|
|
102
|
+
autonomous?: boolean,
|
|
103
|
+
budget?: { maxApiCalls?: number, maxCostUsd?: number, onBudgetExceeded?: string },
|
|
104
|
+
cwd?: string,
|
|
105
|
+
log?: (m: string) => void,
|
|
106
|
+
now?: () => number
|
|
107
|
+
}
|
|
108
|
+
): Promise<number>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Параметри:**
|
|
112
|
+
|
|
113
|
+
- `rest` — позиційні аргументи CLI, можуть містити прапор `--autonomous` плюс `<branch> <task...>`;
|
|
114
|
+
- `deps` — об'єкт ін'єкцій:
|
|
115
|
+
- `runner` — готовий subagent-runner (інакше створюється через `createRunner(deps)`);
|
|
116
|
+
- `verify` — кастомний verify-callback (дефолт: `defaultVerify`);
|
|
117
|
+
- `commit` — кастомний commit-callback (дефолт: `defaultCommit`);
|
|
118
|
+
- `run` — низькорівневий spawn-аналог (пробрасується далі в `ensureWorktree` / `createRunner`);
|
|
119
|
+
- `autonomous` — примусово вмикає budget guard незалежно від `rest`;
|
|
120
|
+
- `budget` — явний конфіг бюджету (інакше читається з `.n-cursor.json`);
|
|
121
|
+
- `cwd` — корінь проєкту для читання конфігу (дефолт: `process.cwd()`);
|
|
122
|
+
- `log` — логер (дефолт: `console.error`);
|
|
123
|
+
- `now` — постачальник часу (дефолт: `Date.now`).
|
|
124
|
+
|
|
125
|
+
**Повертає:** `Promise<number>` — exit code:
|
|
126
|
+
|
|
127
|
+
- `0` — `result.status === 'done'`;
|
|
128
|
+
- `1` — `ensureWorktree` повернув ненульовий код, runner не створився, або executor дав помилку / `BudgetExceeded` / інший fail-стан;
|
|
129
|
+
- `2` — `result.status === 'blocked-on-human'`.
|
|
130
|
+
|
|
131
|
+
**Потік:**
|
|
132
|
+
|
|
133
|
+
1. Підготовка: `log`, `now`, прапор `autonomous` (з `deps.autonomous` або `rest.includes('--autonomous')`), позиційні аргументи (фільтр без `--`-флагів).
|
|
134
|
+
2. `ensureWorktree(positional, deps)` → якщо `code !== 0`, повертає цей же код. Інакше отримуємо `{ worktreeDir, branch, desc, baseCommit }`.
|
|
135
|
+
3. `writeState(statePath, …)` — ініціальний стан із `status: 'in_progress'`, `started_at` у ISO, `metadata.base_commit`, порожнім `plan`.
|
|
136
|
+
4. Створення runner-а: `deps.runner ?? await createRunner(deps)`. Якщо `createRunner` кинув — лог `run: <msg>` і `return 1`.
|
|
137
|
+
5. Якщо `autonomous`: `runner = withBudget(runner, { maxApiCalls: budget.maxApiCalls, log })`.
|
|
138
|
+
6. **try-блок:**
|
|
139
|
+
- `plan = await generatePlan({ runner, task: desc, cwd: worktreeDir })`;
|
|
140
|
+
- `updateState(statePath, s => ({ ...s, plan }))`;
|
|
141
|
+
- `result = await executePlan({ statePath, eventsPath: flowEventsPath(worktreeDir) }, { runner, verify, commit, cwd: worktreeDir, log, now })`;
|
|
142
|
+
- якщо `result.status === 'done'` — лог `'run: build done — далі \`flow release\`'`, `return 0`;
|
|
143
|
+
- якщо `result.status === 'blocked-on-human'` — лог `run: blocked-on-human на кроці <step>`, `return 2`;
|
|
144
|
+
- інакше `return 1`.
|
|
145
|
+
7. **catch:** якщо `error instanceof BudgetExceeded` — лог `run: <msg> — abort`, оновлення стану на `status: 'failed'`, `return 1`. Будь-яка інша помилка — лог і `return 1`.
|
|
146
|
+
|
|
147
|
+
**Side effects:** створення worktree, запис/оновлення `flow.json`, запис подій у `flow.events.jsonl`, виклики LLM-runner, commit-и в worktree, виконання verify-gate-ів.
|
|
148
|
+
|
|
149
|
+
### `resume(_rest, deps?)`
|
|
150
|
+
|
|
151
|
+
**Сигнатура:**
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
async function resume(
|
|
155
|
+
_rest: string[],
|
|
156
|
+
deps?: {
|
|
157
|
+
runner?: object,
|
|
158
|
+
verify?: (cwd: string) => object,
|
|
159
|
+
commit?: (cwd: string, msg: string) => void,
|
|
160
|
+
run?: (cmd: string, args: string[], opts: object) => object,
|
|
161
|
+
cwd?: string,
|
|
162
|
+
log?: (m: string) => void,
|
|
163
|
+
now?: () => number
|
|
164
|
+
}
|
|
165
|
+
): Promise<number>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Параметри:** `_rest` ігнорується; `deps` — як у `run`, мінус `autonomous`/`budget`.
|
|
169
|
+
|
|
170
|
+
**Повертає:** `0` / `1` / `2` — як у `run`.
|
|
171
|
+
|
|
172
|
+
**Потік (Safe-resume, §4.1.7):**
|
|
173
|
+
|
|
174
|
+
1. `cwd = deps.cwd ?? process.cwd()`, `log`, `now`, `run_ = deps.run ?? realRun`.
|
|
175
|
+
2. `state = readState(flowStatePath(cwd))`. Якщо `state` falsy — лог `'resume: стану нема'`, `return 1`.
|
|
176
|
+
3. `openHitl = (state.hitl ?? []).filter(q => !q.answer)`. Якщо `status === 'blocked-on-human'` і `openHitl.length > 0` — лог `resume: ще blocked — N відкритих HITL-питань (заповни answer і повтори)`, `return 2`.
|
|
177
|
+
4. Якщо `!state.plan?.length` — лог `'resume: нема плану'`, `return 1`.
|
|
178
|
+
5. **Скидання часткового доробку:** `run_('git', ['reset', '--hard', 'HEAD'], { cwd })`.
|
|
179
|
+
6. **HITL-злиття:** із відповідей будується `Map<step, answer>`; план переписується так, що **завершені** кроки (`status === 'done'`) не зачіпаються, а **інші** дістають `retry_count: 0` і — якщо є відповідь на цей крок — поле `hint: <answer>`. HITL-питання з відповіддю переводяться у `status: 'answered'`.
|
|
180
|
+
7. Створення runner-а: `deps.runner ?? await createRunner(deps)` (на помилку — `return 1`).
|
|
181
|
+
8. `executePlan(...)` з тими ж дефолтами verify/commit, що й у `run`.
|
|
182
|
+
9. Розбір `result.status`: `done → 0`, `blocked-on-human → 2`, інше → `1`.
|
|
183
|
+
|
|
184
|
+
**Side effects:** `git reset --hard HEAD` (потенційно деструктивно для незакомічених змін!), мутація `flow.json`, виклики LLM, commit-и, verify.
|
|
185
|
+
|
|
186
|
+
### `cancel(_rest, deps?)`
|
|
187
|
+
|
|
188
|
+
**Сигнатура:**
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
async function cancel(
|
|
192
|
+
_rest: string[],
|
|
193
|
+
deps?: { cwd?: string, log?: (m: string) => void }
|
|
194
|
+
): Promise<number>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Параметри:** `_rest` ігнорується; `deps.cwd` (дефолт: `process.cwd()`), `deps.log` (дефолт: `console.error`).
|
|
198
|
+
|
|
199
|
+
**Повертає:** завжди `0`.
|
|
200
|
+
|
|
201
|
+
**Потік:** виклик `cleanupFlowSiblings(cwd)` (зі `./state-store.mjs`) → лог `'cancel: стан і sibling-и прибрано'` → `return 0`.
|
|
202
|
+
|
|
203
|
+
**Side effects:** видалення `flow.json`, `flow.events.jsonl`, lock-файлів навколо worktree.
|
|
204
|
+
|
|
205
|
+
### `repair(rest, deps?)`
|
|
206
|
+
|
|
207
|
+
**Сигнатура:**
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
async function repair(
|
|
211
|
+
rest: string[],
|
|
212
|
+
deps?: {
|
|
213
|
+
run?: (cmd: string, args: string[], opts: object) => object,
|
|
214
|
+
cwd?: string,
|
|
215
|
+
log?: (m: string) => void
|
|
216
|
+
}
|
|
217
|
+
): Promise<number>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Параметри:**
|
|
221
|
+
|
|
222
|
+
- `rest` — аргументи CLI; перевіряється наявність `--discard-step-work`;
|
|
223
|
+
- `deps.run` — низькорівневий spawn (дефолт: `realRun`);
|
|
224
|
+
- `deps.cwd` — корінь worktree (дефолт: `process.cwd()`);
|
|
225
|
+
- `deps.log` — логер (дефолт: `console.error`).
|
|
226
|
+
|
|
227
|
+
**Повертає:** `0` — у двох випадках: успішне жорстке скидання або валідне читання стану (включно з «стану нема»); `1` — стан пошкоджено (виняток при `readState`).
|
|
228
|
+
|
|
229
|
+
**Потік:**
|
|
230
|
+
|
|
231
|
+
1. Якщо `rest.includes('--discard-step-work')`:
|
|
232
|
+
- `run_('git', ['reset', '--hard', 'HEAD'], { cwd })`;
|
|
233
|
+
- лог `'repair: робоче дерево скинуто до HEAD (--discard-step-work)'`;
|
|
234
|
+
- `return 0`.
|
|
235
|
+
2. Інакше try-блок: `state = readState(flowStatePath(cwd))`. Лог `repair: стан валідний (status: <s.status>)` (якщо state truthy) або `'repair: стану нема'` (якщо falsy). `return 0`.
|
|
236
|
+
3. На помилку читання — лог `repair: стан пошкоджено — <msg>. Спробуй \`flow repair --discard-step-work\` або \`flow cancel\`.`, `return 1`.
|
|
237
|
+
|
|
238
|
+
**Side effects:** опціональний `git reset --hard HEAD` (деструктивно), синхронне читання файлу стану.
|
|
239
|
+
|
|
240
|
+
## Залежності
|
|
241
|
+
|
|
242
|
+
### Стандартна бібліотека Node.js
|
|
243
|
+
|
|
244
|
+
- `node:child_process` → `spawnSync` — синхронні git-команди в `defaultCommit`.
|
|
245
|
+
- `node:fs` → `readFileSync` — читання `.n-cursor.json` у `readFlowAutonomous`.
|
|
246
|
+
- `node:path` → `join` — побудова шляху до конфігу.
|
|
247
|
+
- `node:process` → `cwd as processCwd` — дефолтний робочий каталог.
|
|
248
|
+
|
|
249
|
+
### Внутрішні модулі (relative imports)
|
|
250
|
+
|
|
251
|
+
- `./budget.mjs` → `BudgetExceeded`, `withBudget` — клас винятка для перевищення бюджету та обгортка runner-а з guard-ом (§9.4).
|
|
252
|
+
- `./commands.mjs` → `ensureWorktree`, `realRun` — підготовка worktree та реальний spawn-runner.
|
|
253
|
+
- `./events.mjs` → `flowEventsPath` — шлях до журналу подій (`flow.events.jsonl`).
|
|
254
|
+
- `./executor.mjs` → `executePlan` — послідовне виконання плану з verify/commit/HITL.
|
|
255
|
+
- `./planner.mjs` → `generatePlan` — побудова плану через LLM-runner на основі опису задачі.
|
|
256
|
+
- `./reviewer.mjs` → `runReview` — запуск gate-перевірок (verify-callback за замовчуванням).
|
|
257
|
+
- `./state-store.mjs` → `cleanupFlowSiblings`, `flowStatePath`, `readState`, `updateState`, `writeState` — IO навколо `flow.json` та sibling-файлів.
|
|
258
|
+
- `./subagent-runner.mjs` → `createRunner` — фабрика LLM-runner-а (Claude/інший subagent) з ін'єкцій.
|
|
259
|
+
|
|
260
|
+
## Потік виконання / Використання
|
|
261
|
+
|
|
262
|
+
### CLI-команди (диспатчиться зовнішнім роутером)
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
flow run [--autonomous] <branch> "<task...>"
|
|
266
|
+
flow resume
|
|
267
|
+
flow cancel
|
|
268
|
+
flow repair [--discard-step-work]
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Сценарій `flow run --autonomous feat/x "Add tests"`
|
|
272
|
+
|
|
273
|
+
1. **ensureWorktree** створює (або підхоплює) worktree під гілку `feat/x`, повертає `worktreeDir`, базовий комміт, текстовий опис задачі (`desc`).
|
|
274
|
+
2. У worktree пишеться початковий `flow.json` зі `status: 'in_progress'`, `started_at`, `metadata.base_commit`, порожнім планом.
|
|
275
|
+
3. Створюється `runner` (Claude-subagent). У `--autonomous` режимі він обгортається `withBudget` з `maxApiCalls` з `.n-cursor.json` → `flow.autonomous`.
|
|
276
|
+
4. `generatePlan` через LLM-runner будує покроковий план; план зберігається в стані.
|
|
277
|
+
5. `executePlan` ітерує план: для кожного кроку викликає runner-а → verify-gate → commit (через `defaultCommit`); події логуються у `flow.events.jsonl`; стан оновлюється.
|
|
278
|
+
6. Якщо verify падає, executor може відкрити HITL-питання → стан переходить у `blocked-on-human`, `run` повертає `2`.
|
|
279
|
+
7. Якщо `withBudget` вистрелив `BudgetExceeded` — стан стає `failed`, exit `1`.
|
|
280
|
+
|
|
281
|
+
### Сценарій `flow resume`
|
|
282
|
+
|
|
283
|
+
Користувач заповнив `answer` у HITL-питаннях `flow.json` і запускає `flow resume`:
|
|
284
|
+
|
|
285
|
+
1. Зчитується стан, відкриті (без `answer`) HITL → блокує `resume` з кодом `2`.
|
|
286
|
+
2. `git reset --hard HEAD` повертає робоче дерево до останнього коміту (відкочуємо частковий доробок невдалого кроку).
|
|
287
|
+
3. HITL-відповіді стають полями `hint` для відповідних кроків, `retry_count` обнуляється для незавершених кроків.
|
|
288
|
+
4. `executePlan` стартує з того ж списку, але вже з підказками.
|
|
289
|
+
|
|
290
|
+
### Сценарій `flow cancel`
|
|
291
|
+
|
|
292
|
+
Прибирання залишків:
|
|
293
|
+
|
|
294
|
+
- Видаляється `flow.json`, `flow.events.jsonl`, lock-файли — через `cleanupFlowSiblings(cwd)`.
|
|
295
|
+
- Worktree як такий **не видаляється** — це окрема відповідальність команд worktree-менеджмента.
|
|
296
|
+
|
|
297
|
+
### Сценарій `flow repair`
|
|
298
|
+
|
|
299
|
+
- Без аргументів — діагностика: чи стан читається валідно;
|
|
300
|
+
- `--discard-step-work` — жорсткий `git reset --hard HEAD` (свідома втрата незакомічених змін).
|
|
301
|
+
|
|
302
|
+
### Тестування
|
|
303
|
+
|
|
304
|
+
Усе IO ін'єктується через `deps`:
|
|
305
|
+
|
|
306
|
+
```js
|
|
307
|
+
import { run } from './active.mjs'
|
|
308
|
+
|
|
309
|
+
const fakeRunner = {
|
|
310
|
+
/* mock */
|
|
311
|
+
}
|
|
312
|
+
const code = await run(['feat/x', 'task'], {
|
|
313
|
+
runner: fakeRunner,
|
|
314
|
+
verify: () => ({ pass: true, failedOutput: null }),
|
|
315
|
+
commit: () => {},
|
|
316
|
+
run: () => ({ status: 0, stdout: '', stderr: '' }),
|
|
317
|
+
cwd: '/tmp/proj',
|
|
318
|
+
log: () => {},
|
|
319
|
+
now: () => 0
|
|
320
|
+
})
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Це дозволяє тестам не торкатись реальних `git`, LLM чи gate-команд — лише перевіряти контракт переходів стану, exit-коди й послідовність викликів.
|
|
324
|
+
|
|
325
|
+
## Rebuild Test
|
|
326
|
+
|
|
327
|
+
Цей розділ верифікує, що документація відтворює поведінку файлу без звертання до самого `active.mjs`:
|
|
328
|
+
|
|
329
|
+
1. Чотири експорти модуля: `run`, `resume`, `cancel`, `repair` — усі `async`, усі повертають exit code (`0`/`1`/`2`).
|
|
330
|
+
2. У `run` спочатку викликається `ensureWorktree(positional, deps)`; якщо його `code !== 0`, цей самий код повертається без подальших дій.
|
|
331
|
+
3. Прапор `--autonomous` визначається як `deps.autonomous ?? rest.includes('--autonomous')`; у позиційні аргументи `--`-флаги **не** потрапляють (фільтруються).
|
|
332
|
+
4. Конфіг бюджету в `--autonomous` режимі береться з `deps.budget` або з `.n-cursor.json` → `flow.autonomous` (порожній `{}` при будь-якій помилці читання).
|
|
333
|
+
5. Початковий стан у `run` має поля `branch`, `status: 'in_progress'`, `started_at` (ISO від `now()`), `metadata.base_commit`, `plan: []`.
|
|
334
|
+
6. Помилка створення runner-а в `run`/`resume` → лог із префіксом `run:` / `resume:` і `return 1`.
|
|
335
|
+
7. У try-блоці `run` спочатку `generatePlan`, потім `updateState` з планом, потім `executePlan`; усі три отримують `runner` та `worktreeDir`.
|
|
336
|
+
8. Розбір `result.status` у `run` і `resume`: `done → 0`, `blocked-on-human → 2`, інше → `1`.
|
|
337
|
+
9. `BudgetExceeded` у `run`: лог `<msg> — abort`, `updateState(... status: 'failed')`, `return 1`.
|
|
338
|
+
10. `resume` блокує (exit `2`), якщо стан `blocked-on-human` і є HITL без `answer`.
|
|
339
|
+
11. `resume` робить `git reset --hard HEAD` **перед** запуском runner-а — для скидання часткового доробку.
|
|
340
|
+
12. У `resume` HITL-відповіді перетворюються на `hint` тільки для **незавершених** кроків; завершені (`status === 'done'`) не чіпаються.
|
|
341
|
+
13. HITL-питання з `answer` отримують `status: 'answered'` у новому стані.
|
|
342
|
+
14. `cancel` завжди повертає `0`; викликає `cleanupFlowSiblings(cwd)` і логує `'cancel: стан і sibling-и прибрано'`.
|
|
343
|
+
15. `repair --discard-step-work` робить `git reset --hard HEAD` і повертає `0`.
|
|
344
|
+
16. `repair` без аргументів: успішно зчитує стан (або `null`) → `0`; помилка читання → лог із префіксом `repair: стан пошкоджено —` та посиланням на `flow repair --discard-step-work` / `flow cancel`, `return 1`.
|
|
345
|
+
17. `defaultCommit` робить два `spawnSync('git', …)` у `cwd`: `add -A`, потім `commit -m <msg>`.
|
|
346
|
+
18. `defaultVerify` делегує `runReview({ run: realRun, cwd, fingerprint: () => null })`.
|
|
347
|
+
19. Усі логери дефолтяться на `console.error`; усі джерела часу — на `Date.now`; усі `cwd` — на `process.cwd()`.
|
|
348
|
+
20. Лог-повідомлення мають детерміновані префікси: `run:`, `resume:`, `cancel:`, `repair:` — це публічний контракт CLI-виводу для тестів і користувача.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# artifact.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Модуль `artifact.mjs` — це набір **спільних утиліт для фаз `spec` і `plan`** у моделі «Пасивний Турнікет» dispatcher-конвеєра. Він вирішує три задачі:
|
|
6
|
+
|
|
7
|
+
1. **Резолв traceable-артефакту** в каталозі `docs/<kind>/` (де `<kind>` — `specs` або `plans`) з пріоритезацією за хвостом (`slug`) гілки і вибором найсвіжішого файлу за `mtime`.
|
|
8
|
+
2. **Екстракт нумерованих кроків** із секції `## Кроки` плану у структурований масив `{ task, acceptance }`.
|
|
9
|
+
3. **Read-only перевірка цілісності ланцюга артефактів** через CLI `n-cursor trace` (без мутацій).
|
|
10
|
+
|
|
11
|
+
Ключова інваріанта: модуль **не пише** front-matter лінків (`spec.plan`, `plan.spec`, `plan.flow`) — це робить агент згідно з контрактом, описаним у правилі `flow.mdc`. Тут лише **верифікація**, бо мутатор `trace link` свідомо не реалізовано.
|
|
12
|
+
|
|
13
|
+
Файл є чистим ES-модулем (`.mjs`), не має побічних ефектів на рівні імпорту і експортує три іменовані функції.
|
|
14
|
+
|
|
15
|
+
## Експорти / API
|
|
16
|
+
|
|
17
|
+
| Експорт | Тип | Призначення |
|
|
18
|
+
| ----------------- | ---------- | ------------------------------------------------------------- |
|
|
19
|
+
| `resolveArtifact` | `function` | Знаходить актуальний markdown-артефакт у `docs/<kind>/`. |
|
|
20
|
+
| `extractSteps` | `function` | Парсить нумерований список кроків із тексту плану. |
|
|
21
|
+
| `verifyTrace` | `function` | Перевіряє цілісність ланцюга артефактів через runner `trace`. |
|
|
22
|
+
|
|
23
|
+
Внутрішні (не експортовані) константи:
|
|
24
|
+
|
|
25
|
+
| Ім'я | Значення | Призначення |
|
|
26
|
+
| ----------------- | ----------------- | ---------------------------------------------------------------------- |
|
|
27
|
+
| `ACCEPTANCE_MARK` | `'— acceptance:'` | Маркер критерію приймання в рядку кроку (порівняння case-insensitive). |
|
|
28
|
+
| `DIGITS_RE` | `/^\d+$/u` | RegExp для перевірки, що префікс рядка перед `. ` є лише з цифр. |
|
|
29
|
+
|
|
30
|
+
## Функції
|
|
31
|
+
|
|
32
|
+
### `resolveArtifact(cwd, kind, branch)`
|
|
33
|
+
|
|
34
|
+
**Сигнатура**
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
export function resolveArtifact(cwd, kind, branch)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Параметри**
|
|
41
|
+
|
|
42
|
+
| Ім'я | Тип | Обов'язковий | Опис |
|
|
43
|
+
| -------- | ----------------------- | ------------ | ------------------------------------------------------------------------------------------------------ |
|
|
44
|
+
| `cwd` | `string` | так | Абсолютний шлях до кореня worktree. |
|
|
45
|
+
| `kind` | `'specs' \| 'plans'` | так | Підкаталог усередині `docs/` (`docs/specs/` або `docs/plans/`). |
|
|
46
|
+
| `branch` | `string` (опціональний) | ні | Назва гілки задачі (напр. `claude/flow-gate`). Використовується для пріоритезації за хвостом (`slug`). |
|
|
47
|
+
|
|
48
|
+
**Повертає**
|
|
49
|
+
|
|
50
|
+
`string | null` — абсолютний шлях до знайденого `.md`-файлу або `null`, якщо каталог `docs/<kind>/` відсутній чи в ньому немає markdown-файлів.
|
|
51
|
+
|
|
52
|
+
**Алгоритм / поведінка**
|
|
53
|
+
|
|
54
|
+
1. Збирає `dir = join(cwd, 'docs', kind)`.
|
|
55
|
+
2. Якщо `dir` не існує — повертає `null`.
|
|
56
|
+
3. Сканує `dir` і фільтрує лише файли з розширенням `.md`. Якщо їх немає — `null`.
|
|
57
|
+
4. Обчислює `slug` як останній сегмент `branch` після `/` (`'claude/flow-gate' -> 'flow-gate'`). Якщо `branch` не задано — `slug = null`.
|
|
58
|
+
5. Формує `matched` — підмножину файлів, чиї назви **містять `slug`** (через `String.prototype.includes`). Якщо `slug` відсутній — `matched = []`.
|
|
59
|
+
6. Формує `pool`: якщо `matched.length > 0` — `pool = matched`; інакше `pool = md` (усі markdown-файли каталогу).
|
|
60
|
+
7. Серед `pool` обирає **найсвіжіший за `mtimeMs`** (а при рівності `mtime` — за зростанням за іменем, тож `.at(-1)` поверне лексикографічно більше ім'я як tie-breaker).
|
|
61
|
+
8. Повертає `join(dir, best.f)`.
|
|
62
|
+
|
|
63
|
+
**Side effects**
|
|
64
|
+
|
|
65
|
+
- **Тільки читання ФС**: `existsSync`, `readdirSync`, `statSync` із `node:fs`. Жодних записів.
|
|
66
|
+
|
|
67
|
+
**Особливості / нюанси**
|
|
68
|
+
|
|
69
|
+
- Сортування використовує `Array.prototype.toSorted` (іммутабельний варіант `sort`), тож вихідний `pool` не модифікується.
|
|
70
|
+
- Документація у JSDoc наголошує: попередній «лексикографічний» вибір був **хибним** при кількох артефактах на одну дату — це виявлено dogfood'ом.
|
|
71
|
+
- Якщо в `pool` лише один файл — `.at(-1)` поверне його.
|
|
72
|
+
|
|
73
|
+
### `extractSteps(text)`
|
|
74
|
+
|
|
75
|
+
**Сигнатура**
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
export function extractSteps(text)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Параметри**
|
|
82
|
+
|
|
83
|
+
| Ім'я | Тип | Обов'язковий | Опис |
|
|
84
|
+
| ------ | -------- | ------------ | --------------------------------------------------------------------------------------------------- |
|
|
85
|
+
| `text` | `string` | так | Повний вміст markdown-файлу плану (або довільний текст). Приводиться до рядка через `String(text)`. |
|
|
86
|
+
|
|
87
|
+
**Повертає**
|
|
88
|
+
|
|
89
|
+
`{ task: string, acceptance?: string }[]` — масив кроків у тому порядку, в якому вони зустрілися у вхідному тексті. Якщо нічого не знайдено — порожній масив.
|
|
90
|
+
|
|
91
|
+
**Формат, який розпізнається**
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
N. <текст задачі> — acceptance: <критерій приймання>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
де `N` — одне або більше десяткових цифр. Маркер `— acceptance:` розпізнається **case-insensitive** (через `toLowerCase`).
|
|
98
|
+
|
|
99
|
+
Якщо рядок не починається з `<цифри>. `, він **ігнорується** (best-effort, без помилок).
|
|
100
|
+
|
|
101
|
+
**Алгоритм / поведінка**
|
|
102
|
+
|
|
103
|
+
1. Розбиває вхід на рядки за `\n`.
|
|
104
|
+
2. Для кожного рядка обрізає пробіли (`trim`).
|
|
105
|
+
3. Шукає першу появу `. ` (`dot = line.indexOf('. ')`). Якщо `dot <= 0` (немає крапки з пробілом або вона на самому початку) — рядок пропускається.
|
|
106
|
+
4. Перевіряє, що префікс `line.slice(0, dot)` повністю складається з цифр (`DIGITS_RE`). Інакше — пропуск.
|
|
107
|
+
5. Виокремлює `body = line.slice(dot + 2).trim()`.
|
|
108
|
+
6. Шукає в `body.toLowerCase()` позицію маркера `ACCEPTANCE_MARK` (`'— acceptance:'`).
|
|
109
|
+
7. Якщо маркера немає — додає `{ task: body }`.
|
|
110
|
+
8. Якщо маркер є — ділить `body` на `task` (до маркера) і `acceptance` (після маркера), обрізає обидва і додає `{ task, acceptance }`.
|
|
111
|
+
|
|
112
|
+
**Side effects**
|
|
113
|
+
|
|
114
|
+
- Жодних. Чиста функція.
|
|
115
|
+
|
|
116
|
+
**Особливості / нюанси**
|
|
117
|
+
|
|
118
|
+
- Використано `indexOf` замість regex для уникнення **regex-backtracking**, що даватиме лінійну складність на довгих рядках.
|
|
119
|
+
- Маркер `—` — це **em-dash** (U+2014), не звичайний дефіс. Це важливо для збігу.
|
|
120
|
+
- Поле `acceptance` опціональне у вихідних об'єктах: відсутнє, якщо маркер не знайдено.
|
|
121
|
+
- Нумерація кроків зчитується як префікс, але **не повертається** у результат: повертаються лише `task` і опціонально `acceptance`. Сам номер не використовується для впорядкування — порядок повністю визначається порядком появи в тексті.
|
|
122
|
+
|
|
123
|
+
### `verifyTrace(cwd, runTrace)`
|
|
124
|
+
|
|
125
|
+
**Сигнатура**
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
export function verifyTrace(cwd, runTrace)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Параметри**
|
|
132
|
+
|
|
133
|
+
| Ім'я | Тип | Обов'язковий | Опис |
|
|
134
|
+
| ---------- | -------------------------------- | ------------ | ------------------------------------------------------------------------------------- |
|
|
135
|
+
| `cwd` | `string` | так | Абсолютний шлях до кореня worktree, який передається у trace-runner. |
|
|
136
|
+
| `runTrace` | `(cwd: string) => number` (опц.) | ні | Кастомний runner trace, що повертає exit-code. Призначений для **ін'єкції у тестах**. |
|
|
137
|
+
|
|
138
|
+
**Повертає**
|
|
139
|
+
|
|
140
|
+
`boolean` — `true`, якщо exit-code runner-а дорівнює `0` (ланцюг цілісний); `false` — інакше (розрив ланцюга або помилка).
|
|
141
|
+
|
|
142
|
+
**Алгоритм / поведінка**
|
|
143
|
+
|
|
144
|
+
1. Якщо `runTrace` не передано — створюється дефолтний runner, який викликає `runTraceCli([], { cwd: c, log: () => {} })` з модуля `../trace.mjs`. Логування глушиться (`log: () => {}`), тож зовнішніх ефектів у stdout/stderr не буде.
|
|
145
|
+
2. Виконує `run(cwd)` і повертає `=== 0`.
|
|
146
|
+
|
|
147
|
+
**Side effects**
|
|
148
|
+
|
|
149
|
+
- Викликає `runTraceCli` з `../trace.mjs`, який у звичайному режимі **читає** артефакти й їхні front-matter. Жодних мутацій не передбачено — функція декларується як «read-only сигнал».
|
|
150
|
+
- При використанні `runTrace` ін'єкції — side effects цілком визначаються переданим runner-ом.
|
|
151
|
+
|
|
152
|
+
**Особливості / нюанси**
|
|
153
|
+
|
|
154
|
+
- Семантика exit-code узгоджена з CLI: `0` — OK, `1` (або інше ненульове) — розрив.
|
|
155
|
+
- Аргументи для `runTraceCli` фіксовані: порожній масив `[]` (без додаткових прапорців).
|
|
156
|
+
|
|
157
|
+
## Залежності
|
|
158
|
+
|
|
159
|
+
### Зовнішні (Node.js core)
|
|
160
|
+
|
|
161
|
+
- `node:fs`:
|
|
162
|
+
- `existsSync` — перевірка існування каталогу `docs/<kind>/`.
|
|
163
|
+
- `readdirSync` — лістинг файлів у каталозі.
|
|
164
|
+
- `statSync` — отримання `mtimeMs` для сортування.
|
|
165
|
+
- `node:path`:
|
|
166
|
+
- `join` — побудова шляхів `docs/<kind>/<file>.md`.
|
|
167
|
+
|
|
168
|
+
### Внутрішні (проєктні)
|
|
169
|
+
|
|
170
|
+
- `../trace.mjs` → `runTraceCli` — CLI-runner перевірки цілісності trace-ланцюга. Викликається в `verifyTrace`.
|
|
171
|
+
|
|
172
|
+
### Контракти / правила
|
|
173
|
+
|
|
174
|
+
- `flow.mdc` — описує контракт лінкування front-matter (`spec.plan`/`plan.spec`/`plan.flow`), яке робить агент, а не цей модуль.
|
|
175
|
+
- Структура каталогів `docs/specs/` і `docs/plans/` — конвенція «Пасивний Турнікет».
|
|
176
|
+
|
|
177
|
+
## Потік виконання / Використання
|
|
178
|
+
|
|
179
|
+
Модуль є **бібліотечним**: він не запускається самостійно, а імпортується іншими частинами dispatcher-pipeline (фази `spec`/`plan`).
|
|
180
|
+
|
|
181
|
+
### Типовий сценарій 1 — резолв plan-артефакту і парсинг кроків
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
import { resolveArtifact, extractSteps } from './artifact.mjs'
|
|
185
|
+
import { readFileSync } from 'node:fs'
|
|
186
|
+
|
|
187
|
+
const planPath = resolveArtifact(process.cwd(), 'plans', 'claude/flow-gate')
|
|
188
|
+
if (!planPath) {
|
|
189
|
+
// немає docs/plans/ або файлів — фаза має зупинитись
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const text = readFileSync(planPath, 'utf8')
|
|
194
|
+
const steps = extractSteps(text)
|
|
195
|
+
// steps: [{ task: '...', acceptance: '...' }, ...]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Типовий сценарій 2 — read-only перевірка ланцюга перед мерджем
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
import { verifyTrace } from './artifact.mjs'
|
|
202
|
+
|
|
203
|
+
if (!verifyTrace(process.cwd())) {
|
|
204
|
+
// ланцюг spec ↔ plan ↔ flow має розрив — сигнал агенту
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Типовий сценарій 3 — інверсія залежності у тестах
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
import { verifyTrace } from './artifact.mjs'
|
|
212
|
+
|
|
213
|
+
// підставляємо фейковий runner, щоб не викликати реальний CLI
|
|
214
|
+
const ok = verifyTrace('/tmp/fake-cwd', () => 0)
|
|
215
|
+
// ok === true
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Контекст у dispatcher-конвеєрі
|
|
219
|
+
|
|
220
|
+
1. **Фаза `spec`**: викликає `resolveArtifact(cwd, 'specs', branch)` для пошуку специфікації.
|
|
221
|
+
2. **Фаза `plan`**: викликає `resolveArtifact(cwd, 'plans', branch)`, читає файл, потім `extractSteps(text)` — і отримує перелік кроків, які виконуватиме наступна фаза.
|
|
222
|
+
3. **Гейт цілісності**: перед переходом на наступну фазу — `verifyTrace(cwd)` як сигнал, чи зв'язки front-matter не порушені.
|
|
223
|
+
|
|
224
|
+
## Rebuild Test
|
|
225
|
+
|
|
226
|
+
За цією документацією можна **відновити поведінку** модуля без оригінального коду:
|
|
227
|
+
|
|
228
|
+
- Модуль експортує **рівно три функції**: `resolveArtifact`, `extractSteps`, `verifyTrace`.
|
|
229
|
+
- `resolveArtifact(cwd, kind, branch)` шукає `.md` у `docs/<kind>/`, віддає пріоритет файлам, чиє ім'я містить `slug = branch.split('/').pop()`, серед них (або серед усіх, якщо збігу нема) обирає найсвіжіший за `mtime`; повертає абсолютний шлях або `null`.
|
|
230
|
+
- `extractSteps(text)` парсить рядки виду `N. task — acceptance: crit` (em-dash, маркер case-insensitive), повертає масив `{ task, acceptance? }` у порядку появи; невалідні рядки ігноруються.
|
|
231
|
+
- `verifyTrace(cwd, runTrace?)` повертає `true`, якщо `runTrace(cwd) === 0`; за замовчуванням викликає `runTraceCli([], { cwd, log: () => {} })` з `../trace.mjs`.
|
|
232
|
+
- Жодних мутацій ФС, жодного запису front-matter — це робить агент за `flow.mdc`.
|