@shahimat/ai-core 0.0.0-stage → 0.1.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/CHANGELOG.md +50 -0
- package/README.md +248 -2
- package/dist/commands/adapters/index.d.ts +3 -0
- package/dist/commands/adapters/index.js +21 -0
- package/dist/commands/adapters/memory.d.ts +16 -0
- package/dist/commands/adapters/memory.js +135 -0
- package/dist/commands/adapters/render.d.ts +7 -0
- package/dist/commands/adapters/render.js +43 -0
- package/dist/commands/adapters/source.d.ts +21 -0
- package/dist/commands/adapters/source.js +73 -0
- package/dist/commands/adapters/sync.d.ts +14 -0
- package/dist/commands/adapters/sync.js +186 -0
- package/dist/commands/changelog/index.d.ts +3 -0
- package/dist/commands/changelog/index.js +12 -0
- package/dist/commands/changelog/lint.d.ts +5 -0
- package/dist/commands/changelog/lint.js +83 -0
- package/dist/commands/context/context-index.d.ts +7 -0
- package/dist/commands/context/context-index.js +356 -0
- package/dist/commands/context/index.d.ts +3 -0
- package/dist/commands/context/index.js +19 -0
- package/dist/commands/delta/delta.d.ts +110 -0
- package/dist/commands/delta/delta.js +320 -0
- package/dist/commands/delta/index.d.ts +3 -0
- package/dist/commands/delta/index.js +47 -0
- package/dist/commands/docs/index.d.ts +12 -0
- package/dist/commands/docs/index.js +86 -0
- package/dist/commands/expect/head.d.ts +17 -0
- package/dist/commands/expect/head.js +48 -0
- package/dist/commands/expect/html.d.ts +21 -0
- package/dist/commands/expect/html.js +515 -0
- package/dist/commands/expect/index.d.ts +30 -0
- package/dist/commands/expect/index.js +551 -0
- package/dist/commands/expect/results.d.ts +50 -0
- package/dist/commands/expect/results.js +218 -0
- package/dist/commands/expect/serve.d.ts +11 -0
- package/dist/commands/expect/serve.js +146 -0
- package/dist/commands/expect/vitest.config.d.ts +2 -0
- package/dist/commands/expect/vitest.config.js +24 -0
- package/dist/commands/outdated/index.d.ts +13 -0
- package/dist/commands/outdated/index.js +81 -0
- package/dist/commands/prompt-index/index.d.ts +2 -0
- package/dist/commands/prompt-index/index.js +48 -0
- package/dist/commands/role/index.d.ts +2 -0
- package/dist/commands/role/index.js +44 -0
- package/dist/commands/secrets/index.d.ts +3 -0
- package/dist/commands/secrets/index.js +11 -0
- package/dist/commands/secrets/secrets.d.ts +5 -0
- package/dist/commands/secrets/secrets.js +91 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +25 -0
- package/dist/lib/agent.d.ts +14 -0
- package/dist/lib/agent.js +74 -0
- package/dist/lib/claude.d.ts +47 -0
- package/dist/lib/claude.js +134 -0
- package/dist/lib/expect/anchors.d.ts +9 -0
- package/dist/lib/expect/anchors.js +79 -0
- package/dist/lib/expect/batch.d.ts +37 -0
- package/dist/lib/expect/batch.js +178 -0
- package/dist/lib/expect/evidence.d.ts +3 -0
- package/dist/lib/expect/evidence.js +26 -0
- package/dist/lib/expect/index.d.ts +10 -0
- package/dist/lib/expect/index.js +10 -0
- package/dist/lib/expect/judge.d.ts +2 -0
- package/dist/lib/expect/judge.js +45 -0
- package/dist/lib/expect/levels.d.ts +42 -0
- package/dist/lib/expect/levels.js +101 -0
- package/dist/lib/expect/lint.d.ts +12 -0
- package/dist/lib/expect/lint.js +44 -0
- package/dist/lib/expect/model.d.ts +85 -0
- package/dist/lib/expect/model.js +120 -0
- package/dist/lib/expect/pending.d.ts +13 -0
- package/dist/lib/expect/pending.js +15 -0
- package/dist/lib/expect/plan.d.ts +60 -0
- package/dist/lib/expect/plan.js +72 -0
- package/dist/lib/expect/repo.d.ts +52 -0
- package/dist/lib/expect/repo.js +183 -0
- package/dist/lib/expect/schema.d.ts +8 -0
- package/dist/lib/expect/schema.js +48 -0
- package/dist/lib/expect/scope.d.ts +33 -0
- package/dist/lib/expect/scope.js +59 -0
- package/dist/lib/expect/verdicts.d.ts +34 -0
- package/dist/lib/expect/verdicts.js +91 -0
- package/dist/lib/intents.d.ts +2 -0
- package/dist/lib/intents.js +32 -0
- package/dist/lib/mcp.d.ts +10 -0
- package/dist/lib/mcp.js +85 -0
- package/dist/lib/prompt-index.d.ts +40 -0
- package/dist/lib/prompt-index.js +327 -0
- package/dist/lib/protocol.d.ts +39 -0
- package/dist/lib/protocol.js +135 -0
- package/dist/lib/role.d.ts +39 -0
- package/dist/lib/role.js +225 -0
- package/dist/lib/runtime.d.ts +7 -0
- package/dist/lib/runtime.js +9 -0
- package/dist/lib/tree.d.ts +16 -0
- package/dist/lib/tree.js +49 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +132 -0
- package/dist/shared/args.d.ts +20 -0
- package/dist/shared/args.js +1 -0
- package/dist/shared/config.d.ts +31 -0
- package/dist/shared/config.js +136 -0
- package/dist/shared/diagnostics.d.ts +8 -0
- package/dist/shared/diagnostics.js +5 -0
- package/dist/shared/frontmatter.d.ts +3 -0
- package/dist/shared/frontmatter.js +19 -0
- package/dist/shared/guards.d.ts +5 -0
- package/dist/shared/guards.js +17 -0
- package/dist/shared/layout.d.ts +2 -0
- package/dist/shared/layout.js +2 -0
- package/dist/shared/node.d.ts +27 -0
- package/dist/shared/node.js +1 -0
- package/dist/shared/report.d.ts +3 -0
- package/dist/shared/report.js +15 -0
- package/dist/shared/schema.d.ts +10 -0
- package/dist/shared/schema.js +24 -0
- package/package.json +80 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Журнал @shahimat/ai-core
|
|
2
|
+
|
|
3
|
+
Свежее сверху. Одна версия — список новых команд и проверок.
|
|
4
|
+
|
|
5
|
+
## 0.1.1
|
|
6
|
+
|
|
7
|
+
- Дельта, попавшая в коммит после сбоя сессии, закрывается: пункт
|
|
8
|
+
засчитывается, если цель изменена в коммитах с появления дельты;
|
|
9
|
+
`delta check` предупреждает о закоммиченной дельте.
|
|
10
|
+
- `delta check --name` и `delta close --name`: нарушения в чужих дельтах
|
|
11
|
+
— предупреждение со списком файлов, а не провал.
|
|
12
|
+
- Дельта не прошла схему — ошибка схемы идёт первой, производные
|
|
13
|
+
нарушения «без revise» по её целям свёрнуты в одну строку.
|
|
14
|
+
- peer `vitest` — `>=3`: проекты на vitest 3 ставятся без ERESOLVE.
|
|
15
|
+
|
|
16
|
+
## 0.1.0
|
|
17
|
+
|
|
18
|
+
- `delta close --name <дельта>` закрывает только эту дельту: чужая
|
|
19
|
+
невыполненная больше не валит close. Принятые `revise` до коммита
|
|
20
|
+
помнит `context/.delta/.accepted.json` — следующая сессия не падает
|
|
21
|
+
«знание без revise». `target` с якорем `файл#поле` сверяется по файлу,
|
|
22
|
+
пункт о файле внутри `context/.delta/` считается выполненным.
|
|
23
|
+
|
|
24
|
+
- Прогон роли прозрачен: журнал stream-json пишется в файл по мере
|
|
25
|
+
работы, `onProgress` получает строки «роль X: шаг N — инструмент»,
|
|
26
|
+
таймаут роли (по умолчанию 30 мин, `HARNESS_ROLE_TIMEOUT_MIN`)
|
|
27
|
+
останавливает `claude -p` с понятной причиной вместо тихого ожидания.
|
|
28
|
+
|
|
29
|
+
- Роли: `askRole(root, name, task, { transcript, addDirs })` — полный
|
|
30
|
+
stream-json прогона в файл журнала и доступ роли к каталогам вне
|
|
31
|
+
проекта (`--add-dir`, копия репозитория job).
|
|
32
|
+
- Hook `ops-brief`: `adapters sync` при `hooks: [ops-brief]` ставит
|
|
33
|
+
`ai ops brief` в `SessionStart` (`startup`, `resume`, `compact`).
|
|
34
|
+
- `CommandArgs`: `all`, `every`, `until`, `job`, `timeout` — опции
|
|
35
|
+
рантайма `ops`.
|
|
36
|
+
|
|
37
|
+
- Пакет выделен из `@shahimat/harness`: команды (`index`, `adapters`,
|
|
38
|
+
`changelog`, `context`, `delta`, `expect`, `secrets`, `outdated`,
|
|
39
|
+
`role`), скрипты (`check`, `refresh`), механизм ожиданий
|
|
40
|
+
(`@shahimat/ai-core/expect`) и роли (протокол, MCP-канал, запуск
|
|
41
|
+
`claude -p`). CLI — `ai-core`; автоматы остались в
|
|
42
|
+
`@shahimat/harness`.
|
|
43
|
+
- Конфиг проекта — `context/ai-core.yml` (было `context/harness.yml`);
|
|
44
|
+
ключа `version` нет — версии пакетов в `package.json` проекта,
|
|
45
|
+
`outdated` сверяет оттуда `@shahimat/ai-core` и `@shahimat/harness`.
|
|
46
|
+
- `docs guard` — hook `PreToolUse`: правка документации и генерируемых
|
|
47
|
+
файлов в основной сессии — отказ с кодом 2; `adapters sync` ставит
|
|
48
|
+
его в `.claude/settings.json` при `hooks: [docs-guard]`.
|
|
49
|
+
- Метка генерируемых файлов и заголовки индексов — «generated by
|
|
50
|
+
ai-core …».
|
package/README.md
CHANGED
|
@@ -1,3 +1,249 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @shahimat/ai-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Ядро для проектов на контракте
|
|
4
|
+
[`agent-operational-contract-v3`](https://github.com/Shahimat/ai-prompts/blob/main/prompts/productivity/agent-operational-contract-v3.md):
|
|
5
|
+
механические проверки и генерация, механизм ожиданий и роли, чтобы агент
|
|
6
|
+
не держал их в памяти. Библиотека без бинаря: команды запускает CLI `ai`
|
|
7
|
+
([`@shahimat/cli`](https://www.npmjs.com/package/@shahimat/cli)), автоматы
|
|
8
|
+
поверх них — [`@shahimat/harness`](https://www.npmjs.com/package/@shahimat/harness).
|
|
9
|
+
|
|
10
|
+
Node ≥ 24. macOS, Linux, Windows.
|
|
11
|
+
|
|
12
|
+
## Запуск
|
|
13
|
+
|
|
14
|
+
Пакет ставится в `devDependencies` проекта рядом с `@shahimat/cli`,
|
|
15
|
+
точные версии; команды — через `npx ai <команда>`.
|
|
16
|
+
|
|
17
|
+
## Устройство
|
|
18
|
+
|
|
19
|
+
Пакет делится на слои, зависимости идут только вниз:
|
|
20
|
+
|
|
21
|
+
- **команды** — атомарные операции: прочитали файлы, проверили или
|
|
22
|
+
сгенерировали, вернули код. Друг друга не вызывают.
|
|
23
|
+
|
|
24
|
+
Скрипты (последовательности команд) — в `@shahimat/cli`.
|
|
25
|
+
|
|
26
|
+
Под ними — общий `shared` (guards, конфиг, проверка JSON Schema) и
|
|
27
|
+
библиотека `lib` (`@shahimat/ai-core/expect`, индекс промптов, роли).
|
|
28
|
+
Автоматы (`@shahimat/harness`) — слой выше: импортируют публичный API
|
|
29
|
+
`ai-core`, обратной зависимости нет.
|
|
30
|
+
|
|
31
|
+
У каждой команды и операции автомата объявлен эффект: **query** не
|
|
32
|
+
меняет файлов (кроме кэша по содержимому вне git), **mutation** пишет.
|
|
33
|
+
Эффект скрипта выводится из его шагов. CI и модель внутри состояния
|
|
34
|
+
автомата получают только query.
|
|
35
|
+
|
|
36
|
+
## Команды
|
|
37
|
+
|
|
38
|
+
Mutation — `context index`, `adapters sync`, `delta close`,
|
|
39
|
+
`expect init`, `expect judge`, `expect report --html`, `index build`,
|
|
40
|
+
`role run`, `role channel`; остальные — query.
|
|
41
|
+
|
|
42
|
+
- `ai secrets scan [--file <path>]` — секреты (ключи API, токены,
|
|
43
|
+
пароли, приватные ключи) в отслеживаемых git файлах проекта или в
|
|
44
|
+
одном файле по пути; в выводе — путь, строка и тип, без значения. В
|
|
45
|
+
`ai check` включена по умолчанию.
|
|
46
|
+
- `ai changelog lint` — `CHANGELOG.md`: строка
|
|
47
|
+
`- YYYY-MM-DD · [tag] суть · почему` до 120 символов, тег из списка,
|
|
48
|
+
порядок дат.
|
|
49
|
+
- `ai context index` — генерация `context/index.yml` из слоёв
|
|
50
|
+
документации (entities, behavior, manuals, conventions, слои модулей).
|
|
51
|
+
- `ai context check` — сверка `context/index.yml` со слоями
|
|
52
|
+
документации без записи.
|
|
53
|
+
- `ai adapters sync` — файлы под Claude Code и Codex из
|
|
54
|
+
`AGENTS.md` и `.agents/`: `CLAUDE.md`, копии skills, субагенты,
|
|
55
|
+
выключенная memory агента; при `hooks: [docs-guard]` — hook
|
|
56
|
+
`ai docs guard` в `.claude/settings.json` (свои hooks файла
|
|
57
|
+
сохраняются).
|
|
58
|
+
- `ai adapters check` — сверка файлов адаптеров с `AGENTS.md` и
|
|
59
|
+
`.agents/` без записи.
|
|
60
|
+
- `ai expect init` — каркас ожиданий в проекте: точка входа
|
|
61
|
+
`expectations/index.expect.ts`, пример файла с бюджетом, npm-скрипты,
|
|
62
|
+
`.gitignore`.
|
|
63
|
+
- `ai expect report | lint | judge [--apply | --run] <file>` — ожидания в
|
|
64
|
+
форме vitest: дерево со статусами, проверка заголовков, пакетная
|
|
65
|
+
смысловая проверка судьёй-агентом с кэшем вердиктов. `judge` пишет
|
|
66
|
+
задания в `.judge/` для субагента-судьи, `--apply` применяет ответы;
|
|
67
|
+
`--run` отвечает на задания ролью `judge` (см. «Роли») и сразу
|
|
68
|
+
применяет. `<file>` — одна
|
|
69
|
+
точка входа `*.expect.ts`; остальные файлы ожиданий попадают в прогон
|
|
70
|
+
только через `import` в ней. Конфиг vitest — из пакета, проекту свой
|
|
71
|
+
не нужен. Отчёт, бюджет и проверка заголовков — по модулю, где
|
|
72
|
+
объявлено ожидание; вердикты и задания судьи — рядом с точкой входа
|
|
73
|
+
(`<каталог>/.verdicts.json`, `<каталог>/.judge/`). Помощники для
|
|
74
|
+
проверок — `@shahimat/harness/expect` (нужен vitest в проекте):
|
|
75
|
+
`crit` / `warn` / `note` / `budget`, `describe`, `expectAtMost` (info
|
|
76
|
+
при > 90% порога), зонды по репозиторию, `judged`, проверка JSON
|
|
77
|
+
Schema (`expectSchema`, `dataFiles`, `frontmatters`). Общий блок
|
|
78
|
+
ожиданий вне `*.expect.ts` объявляет разделы через `describe` из
|
|
79
|
+
`@shahimat/harness/expect`: так ожидания приписываются файлу, который
|
|
80
|
+
блок применил. `pattern(key, apply)` — паттерн ожиданий: функция с
|
|
81
|
+
параметрами, которая регистрирует ожидания для переданных целей (цели
|
|
82
|
+
— в заголовке, якоря доказательств — параметром); ожидания
|
|
83
|
+
приписываются применившему файлу, в HTML у них виден ключ паттерна.
|
|
84
|
+
Паттерны для любых проектов — `@shahimat/expectations`, для одного
|
|
85
|
+
репозитория — его `expectations/shared/`.
|
|
86
|
+
`report <file> --html <out>` — тот же отчёт HTML-файлом,
|
|
87
|
+
`serve <file> [--port <n>]` — с локального сервера (см. «План в форме
|
|
88
|
+
ожиданий»).
|
|
89
|
+
- `ai delta check | close` — дельта проектной документации в
|
|
90
|
+
`context/.delta/*.yml` (пункты `add` / `refine` / `revise`). `check`
|
|
91
|
+
сверяет слои документации с `HEAD`: удалённое или изменённое знание
|
|
92
|
+
без пункта `revise` — нарушение; дельта не прошла схему — её ошибка
|
|
93
|
+
первой, производные нарушения по её целям свёрнуты. `close` после
|
|
94
|
+
приёмки (check чист, пункты выполнены) удаляет дельту и печатает
|
|
95
|
+
строки `revise` для тела коммита. `--name <дельта>` сужает обе
|
|
96
|
+
команды до одной дельты: нарушения чужих — предупреждение. Дельта,
|
|
97
|
+
попавшая в коммит, — предупреждение; её пункт засчитывается, если цель
|
|
98
|
+
изменена в коммитах с появления дельты.
|
|
99
|
+
- `ai role run <name> --file <задание> [--out <ответ>]` — роль из
|
|
100
|
+
`.agents/agents/<name>.md` (в источнике — `agents/<name>.md`)
|
|
101
|
+
headless: текст задания — на stdin, итог — сообщением протокола в
|
|
102
|
+
`--out` или stdout (см. «Роли»).
|
|
103
|
+
- `ai role channel <message> --out <файл>` — MCP-сервер (stdio)
|
|
104
|
+
канала роли с одним инструментом `submit_result`: проверяет ответ по
|
|
105
|
+
схеме сообщения, невалидный возвращает модели ошибкой с путём к полю,
|
|
106
|
+
валидный пишет в `--out`. Запускает его `role run`, не человек.
|
|
107
|
+
- `ai outdated` — версии `@shahimat/ai-core` и `@shahimat/harness` из
|
|
108
|
+
`package.json` проекта против последних в npm и записи их журналов
|
|
109
|
+
между ними (код 1 — есть новее).
|
|
110
|
+
- `ai docs guard` — hook `PreToolUse` для Claude Code: JSON hook на
|
|
111
|
+
stdin; `Edit` / `Write` в документацию (`CHANGELOG.md`,
|
|
112
|
+
`context/project.yml`, `context/conventions/`, `docs/`, слои модулей
|
|
113
|
+
из `provides.layers`, `context/.delta/`) и в генерируемые файлы
|
|
114
|
+
(`context/index.yml`, `CLAUDE.md`, `.claude/skills/`, `.claude/agents/`,
|
|
115
|
+
`.codex/agents/`) — отказ, код 2 и подсказка про `ai docs-delta`;
|
|
116
|
+
остальное — 0. Ставит его `adapters sync` при `hooks: [docs-guard]`.
|
|
117
|
+
- `ai index build | check --raw-base <url>` — индекс
|
|
118
|
+
`prompts/index.yml` репозитория промптов.
|
|
119
|
+
|
|
120
|
+
## План в форме ожиданий
|
|
121
|
+
|
|
122
|
+
План — тот же файл ожиданий, а не отдельный документ. Файл описывает
|
|
123
|
+
конечное состояние; чтобы его изменить, в файл добавляются блоки — `fsm`,
|
|
124
|
+
`steps` и другие. План переходит в трекинг (заглушки сменяются
|
|
125
|
+
проверками), трекинг — в новое конечное состояние. Вид страницы задают
|
|
126
|
+
блоки: нет блока — нет его диаграммы.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
plan({ name: 'plan-review: стол и оркестратор' });
|
|
130
|
+
|
|
131
|
+
const planReview = fsm('plan-review', {
|
|
132
|
+
initial: 'draft',
|
|
133
|
+
limits: { fix: 3 },
|
|
134
|
+
states: {
|
|
135
|
+
draft: { actor: 'model', on: { questions: 'clarify', plan: 'review' } },
|
|
136
|
+
clarify: { actor: 'user', on: { answered: 'draft' } },
|
|
137
|
+
review: { actor: 'model', on: { pass: 'approved', fail: [{ to: 'draft', count: 'fix' }, 'approved'] } },
|
|
138
|
+
approved: { final: true },
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
steps(() => {
|
|
143
|
+
step('T1', { title: 'Headless draft', why: 'нет TTY' }, () => {
|
|
144
|
+
planReview.state('draft', () => {
|
|
145
|
+
crit.wip('ответ планера — план или вопросы по схеме', () => Check.draftAnswer());
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
step('T2', { title: 'Вопросы', after: ['T1'] }, () => {
|
|
149
|
+
planReview.transition('draft', 'questions', () => {
|
|
150
|
+
crit.todo('вопросы нумерованы, у каждого дефолт с обоснованием');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
planReview.whole(() => {
|
|
156
|
+
crit('из любого состояния достижимо approved', () => Graph.canReach(planReview, ['approved']));
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- `plan({ name })` — имя плана: сессия plan-review и вкладка браузера.
|
|
161
|
+
Шапка страницы — только `info.title`; без неё `expect report`
|
|
162
|
+
просит её добавить.
|
|
163
|
+
- Заглушки задают статус ожидания: `crit.todo(title)` / `warn.todo` — не
|
|
164
|
+
взято; `crit.wip(title, fn?)` / `warn.wip` — в работе: проверка
|
|
165
|
+
исполняется, если она есть, провал виден (`◐`), но не блокирует;
|
|
166
|
+
без заглушки проверка блокирует.
|
|
167
|
+
- `steps(() => { step(id, { title, after, why }, body) })` — шаги плана;
|
|
168
|
+
ожидания шага — его DoD, `step` вне `steps` — ошибка. Статус шага
|
|
169
|
+
выводится из ожиданий: все `todo` — не начат, все без заглушек и
|
|
170
|
+
зелёные — готов, иначе — в работе. Нарушение плана (crit): готовый
|
|
171
|
+
шаг раньше шагов из `after`, неизвестный шаг в `after`, цикл, повтор
|
|
172
|
+
id. На странице — UML-диаграмма активностей: старт, шаги, развилки и
|
|
173
|
+
слияния по `after`, финал.
|
|
174
|
+
- `fsm(name, { initial, limits, states })` — автомат данными. Имена
|
|
175
|
+
состояний и событий выводятся из литерала: опечатка в `state`,
|
|
176
|
+
`transition`, переходе или `initial` — ошибка typecheck. `state`,
|
|
177
|
+
`transition` и `whole` привязывают ожидания к узлу, переходу и
|
|
178
|
+
автомату целиком; на странице — схема Graphviz.
|
|
179
|
+
- Блоки и ожидания из импортированных файлов попадают на страницу в
|
|
180
|
+
разделе своего модуля; у ожидания из общего блока видно, где оно
|
|
181
|
+
определено.
|
|
182
|
+
- Зонды: `Graph.sameAs(m, def)` — граф совпадает с `harness.yaml`
|
|
183
|
+
(`def` — результат `parseMachine`); `Graph.canReach(m, targets)` — из
|
|
184
|
+
каждого состояния достижима цель; `Graph.onlyVia(m, target, via)` — в
|
|
185
|
+
`target` из `initial` не попасть в обход `via`.
|
|
186
|
+
- `ai expect report <file> --html <out>` — HTML без сети
|
|
187
|
+
и без JS: диаграммы блоков — SVG Graphviz (wasm, при генерации); узел и переход ведут к карточке, цвет — статус ожиданий;
|
|
188
|
+
новые, изменённые и удалённые относительно HEAD ожидания отмечены.
|
|
189
|
+
Файл — производный вид, источник — файл ожиданий.
|
|
190
|
+
- `ai expect serve <file> [--port <n>]` — та же страница
|
|
191
|
+
с `http://127.0.0.1:<port>/` (по умолчанию 4173): собирается при
|
|
192
|
+
каждом запросе, правка файла ожиданий видна после перезагрузки, на
|
|
193
|
+
диск ничего не пишется. Так артефакт плана — только файл ожиданий.
|
|
194
|
+
|
|
195
|
+
## Роли
|
|
196
|
+
|
|
197
|
+
Роль — субагент из `agents/<name>.md` источника (в проекте —
|
|
198
|
+
`.agents/agents/`): промпт, `tools`, `model` (`opus` по умолчанию,
|
|
199
|
+
`sonnet`, `haiku`) и `output` — сообщение протокола, которым роль
|
|
200
|
+
возвращает итог. Одна и та же роль работает субагентом в сессии агента
|
|
201
|
+
(адаптеры генерируют `.claude/agents/`, `.codex/agents/`) и
|
|
202
|
+
headless-исполнителем в harness.
|
|
203
|
+
|
|
204
|
+
- Протокол — JSON Schema сообщений: `result` (итог работы: статус,
|
|
205
|
+
изменённое, не прошедшее, вопросы, отчёт), `verdict` (ответ судьи:
|
|
206
|
+
вердикты пакета или проверка заголовков), `question` (блокирующий
|
|
207
|
+
вопрос человеку; канала для него пока нет). Схемы — `PROTOCOL` из
|
|
208
|
+
пакета.
|
|
209
|
+
- Запуск — `claude -p` из Claude Code пользователя (его вход):
|
|
210
|
+
`--setting-sources ""`, `--strict-mcp-config`, единственный MCP —
|
|
211
|
+
канал `role channel` с `submit_result`, права — `tools` роли и
|
|
212
|
+
`submit_result`, режим `dontAsk`, задание — на stdin. Ответ текстом
|
|
213
|
+
harness не читает: нет вызова `submit_result` с валидным ответом —
|
|
214
|
+
ошибка роли.
|
|
215
|
+
- Вход роли docs-keeper — схема `INTENTS_SCHEMA`: намерения `kind`
|
|
216
|
+
(`decision`, `fact`, `rule`, `behavior-change`, `task`, `manual`),
|
|
217
|
+
`summary`, `evidence`. Ответ судьи о документации — `verdict` с полем
|
|
218
|
+
`conflicts` (`file_a`, `quote_a`, `file_b`, `quote_b`, `reason`).
|
|
219
|
+
- Роли пакета используют: `expect judge --run` (роль `judge`),
|
|
220
|
+
`docs-delta` (роль `docs-keeper` в `apply` с `--file`, роль `judge` в
|
|
221
|
+
`review`).
|
|
222
|
+
|
|
223
|
+
Коды выхода: `0` — ok, `1` — нарушения, `2` — ошибка вызова или
|
|
224
|
+
конфигурации.
|
|
225
|
+
|
|
226
|
+
## `context/ai-core.yml`
|
|
227
|
+
|
|
228
|
+
```yaml
|
|
229
|
+
adapters: [claude-code, codex]
|
|
230
|
+
hooks: [docs-guard]
|
|
231
|
+
checks:
|
|
232
|
+
changelog:
|
|
233
|
+
max_line: 120
|
|
234
|
+
tags: [arch, stack, dep, milestone]
|
|
235
|
+
context-index: {}
|
|
236
|
+
adapters: {}
|
|
237
|
+
secrets:
|
|
238
|
+
allow:
|
|
239
|
+
- path: docs/aws.md
|
|
240
|
+
line: 12
|
|
241
|
+
reason: пример ключа из документации AWS
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`secrets` включена, даже если ключа нет; `secrets: false` — выключить.
|
|
245
|
+
Исключение без `reason` — ошибка конфига.
|
|
246
|
+
|
|
247
|
+
## Лицензия
|
|
248
|
+
|
|
249
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { applyActions, checkAdapters, computeSync } from './sync.js';
|
|
2
|
+
import { CONFIG_PATH, loadConfig } from '#shared/config';
|
|
3
|
+
import { UsageError } from '#shared/diagnostics';
|
|
4
|
+
import { report } from '#shared/report';
|
|
5
|
+
export async function runAdapters(command, args) {
|
|
6
|
+
if (command !== 'sync' && command !== 'check')
|
|
7
|
+
throw new UsageError(`неизвестная команда: adapters ${command ?? ''}`.trimEnd());
|
|
8
|
+
const config = await loadConfig(args.root);
|
|
9
|
+
if (config === null)
|
|
10
|
+
throw new UsageError(`нет ${CONFIG_PATH}: список adapters берётся оттуда`);
|
|
11
|
+
if (command === 'check')
|
|
12
|
+
return report('adapters check', await checkAdapters(args.root, config.adapters, config.hooks));
|
|
13
|
+
const { actions, diagnostics } = await computeSync(args.root, config.adapters, config.hooks);
|
|
14
|
+
await applyActions(args.root, actions);
|
|
15
|
+
for (const action of actions)
|
|
16
|
+
console.error(`${action.op === 'write' ? 'wrote' : 'deleted'} ${action.path}`);
|
|
17
|
+
if (actions.length === 0)
|
|
18
|
+
console.error('adapters: без изменений');
|
|
19
|
+
return report('adapters', diagnostics);
|
|
20
|
+
}
|
|
21
|
+
export { applyActions, checkAdapters, computeSync } from './sync.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Diagnostic } from '#shared/diagnostics';
|
|
2
|
+
import type { HookKey } from '#shared/config';
|
|
3
|
+
import type { Action } from './sync.ts';
|
|
4
|
+
export declare const CLAUDE_SETTINGS = ".claude/settings.json";
|
|
5
|
+
export declare const CODEX_CONFIG = ".codex/config.toml";
|
|
6
|
+
export declare function codexConfig(): string;
|
|
7
|
+
export declare const DOCS_GUARD_COMMAND = "node node_modules/@shahimat/cli/dist/bin.js docs guard";
|
|
8
|
+
export declare const OPS_BRIEF_COMMAND = "node node_modules/@shahimat/cli/dist/bin.js ops brief";
|
|
9
|
+
export declare function claudeMemory(root: string, hookKeys?: readonly HookKey[]): Promise<{
|
|
10
|
+
actions: Action[];
|
|
11
|
+
diagnostics: Diagnostic[];
|
|
12
|
+
}>;
|
|
13
|
+
export declare function codexMemory(root: string): Promise<{
|
|
14
|
+
actions: Action[];
|
|
15
|
+
diagnostics: Diagnostic[];
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// expect:memory-off
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { errorCode, errorMessage, isRecord } from '#shared/guards';
|
|
5
|
+
import { GENERATED_MARK } from './render.js';
|
|
6
|
+
export const CLAUDE_SETTINGS = '.claude/settings.json';
|
|
7
|
+
export const CODEX_CONFIG = '.codex/config.toml';
|
|
8
|
+
const CODEX_MEMORY_KEYS = [
|
|
9
|
+
'features.memories',
|
|
10
|
+
'memories.use_memories',
|
|
11
|
+
'memories.generate_memories',
|
|
12
|
+
];
|
|
13
|
+
export function codexConfig() {
|
|
14
|
+
return [
|
|
15
|
+
`# ${GENERATED_MARK} — do not edit; memory агента запрещена контрактом`,
|
|
16
|
+
'[features]',
|
|
17
|
+
'memories = false',
|
|
18
|
+
'',
|
|
19
|
+
'[memories]',
|
|
20
|
+
'use_memories = false',
|
|
21
|
+
'generate_memories = false',
|
|
22
|
+
'',
|
|
23
|
+
].join('\n');
|
|
24
|
+
}
|
|
25
|
+
async function readOrNull(abs) {
|
|
26
|
+
try {
|
|
27
|
+
return await readFile(abs, 'utf8');
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (errorCode(error) === 'ENOENT')
|
|
31
|
+
return null;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export const DOCS_GUARD_COMMAND = 'node node_modules/@shahimat/cli/dist/bin.js docs guard';
|
|
36
|
+
const DOCS_GUARD_MATCHER = 'Edit|Write|MultiEdit|NotebookEdit';
|
|
37
|
+
export const OPS_BRIEF_COMMAND = 'node node_modules/@shahimat/cli/dist/bin.js ops brief';
|
|
38
|
+
const OPS_BRIEF_MATCHER = 'startup|resume|compact';
|
|
39
|
+
const HOOK_SPECS = {
|
|
40
|
+
'docs-guard': { event: 'PreToolUse', matcher: DOCS_GUARD_MATCHER, command: DOCS_GUARD_COMMAND },
|
|
41
|
+
'ops-brief': { event: 'SessionStart', matcher: OPS_BRIEF_MATCHER, command: OPS_BRIEF_COMMAND },
|
|
42
|
+
};
|
|
43
|
+
function withHook(data, spec) {
|
|
44
|
+
const hooks = isRecord(data['hooks']) ? data['hooks'] : {};
|
|
45
|
+
const current = hooks[spec.event];
|
|
46
|
+
const list = Array.isArray(current) ? current : [];
|
|
47
|
+
const present = list.some((entry) => isRecord(entry) &&
|
|
48
|
+
Array.isArray(entry['hooks']) &&
|
|
49
|
+
entry['hooks'].some((h) => isRecord(h) && h['command'] === spec.command));
|
|
50
|
+
if (present)
|
|
51
|
+
return data;
|
|
52
|
+
const hook = { matcher: spec.matcher, hooks: [{ type: 'command', command: spec.command }] };
|
|
53
|
+
return { ...data, hooks: { ...hooks, [spec.event]: [...list, hook] } };
|
|
54
|
+
}
|
|
55
|
+
export async function claudeMemory(root, hookKeys = []) {
|
|
56
|
+
const desired = (data) => {
|
|
57
|
+
let next = { ...data, autoMemoryEnabled: false };
|
|
58
|
+
for (const key of hookKeys)
|
|
59
|
+
next = withHook(next, HOOK_SPECS[key]);
|
|
60
|
+
return next;
|
|
61
|
+
};
|
|
62
|
+
const current = await readOrNull(path.join(root, CLAUDE_SETTINGS));
|
|
63
|
+
const write = (data) => ({
|
|
64
|
+
op: 'write',
|
|
65
|
+
path: CLAUDE_SETTINGS,
|
|
66
|
+
content: Buffer.from(`${JSON.stringify(data, null, 2)}\n`, 'utf8'),
|
|
67
|
+
});
|
|
68
|
+
if (current === null)
|
|
69
|
+
return { actions: [write(desired({}))], diagnostics: [] };
|
|
70
|
+
let data;
|
|
71
|
+
try {
|
|
72
|
+
data = JSON.parse(current);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
actions: [],
|
|
77
|
+
diagnostics: [{ file: CLAUDE_SETTINGS, message: `невалидный JSON: ${errorMessage(error)}` }],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (!isRecord(data)) {
|
|
81
|
+
return {
|
|
82
|
+
actions: [],
|
|
83
|
+
diagnostics: [{ file: CLAUDE_SETTINGS, message: 'корень должен быть объектом' }],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const next = desired(data);
|
|
87
|
+
if (JSON.stringify(next) === JSON.stringify(data))
|
|
88
|
+
return { actions: [], diagnostics: [] };
|
|
89
|
+
return { actions: [write(next)], diagnostics: [] };
|
|
90
|
+
}
|
|
91
|
+
function scanToml(text) {
|
|
92
|
+
const values = new Map();
|
|
93
|
+
let table = '';
|
|
94
|
+
for (const raw of text.replace(/\r\n?/g, '\n').split('\n')) {
|
|
95
|
+
const line = raw.replace(/\s+#.*$/, '').trim();
|
|
96
|
+
if (line === '' || line.startsWith('#'))
|
|
97
|
+
continue;
|
|
98
|
+
const header = /^\[([^[\]]+)\]$/.exec(line);
|
|
99
|
+
if (header !== null) {
|
|
100
|
+
table = (header[1] ?? '').trim();
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const pair = /^([A-Za-z0-9_.-]+)\s*=\s*(.+)$/.exec(line);
|
|
104
|
+
if (pair === null)
|
|
105
|
+
continue;
|
|
106
|
+
const key = table === '' ? (pair[1] ?? '') : `${table}.${pair[1] ?? ''}`;
|
|
107
|
+
values.set(key, (pair[2] ?? '').trim());
|
|
108
|
+
}
|
|
109
|
+
return values;
|
|
110
|
+
}
|
|
111
|
+
export async function codexMemory(root) {
|
|
112
|
+
const current = await readOrNull(path.join(root, CODEX_CONFIG));
|
|
113
|
+
const expected = codexConfig();
|
|
114
|
+
if (current === null || current.includes(GENERATED_MARK)) {
|
|
115
|
+
if (current === expected)
|
|
116
|
+
return { actions: [], diagnostics: [] };
|
|
117
|
+
return {
|
|
118
|
+
actions: [{ op: 'write', path: CODEX_CONFIG, content: Buffer.from(expected, 'utf8') }],
|
|
119
|
+
diagnostics: [],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const values = scanToml(current);
|
|
123
|
+
const missing = CODEX_MEMORY_KEYS.filter((key) => values.get(key) !== 'false');
|
|
124
|
+
if (missing.length === 0)
|
|
125
|
+
return { actions: [], diagnostics: [] };
|
|
126
|
+
return {
|
|
127
|
+
actions: [],
|
|
128
|
+
diagnostics: [
|
|
129
|
+
{
|
|
130
|
+
file: CODEX_CONFIG,
|
|
131
|
+
message: `написан вручную, memory не выключена: добавь ${missing.map((k) => `${k} = false`).join(', ')}`,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentDef } from './source.ts';
|
|
2
|
+
export declare const GENERATED_MARK = "generated by ai adapters sync";
|
|
3
|
+
export declare const SKILL_MARKER_FILE = ".harness-generated";
|
|
4
|
+
export declare function claudeMd(): string;
|
|
5
|
+
export declare function skillMarker(name: string): string;
|
|
6
|
+
export declare function claudeAgent(agent: AgentDef): string;
|
|
7
|
+
export declare function codexAgent(agent: AgentDef): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// expect:claude-md-import
|
|
2
|
+
import { stringify } from 'yaml';
|
|
3
|
+
export const GENERATED_MARK = 'generated by ai adapters sync';
|
|
4
|
+
export const SKILL_MARKER_FILE = '.harness-generated';
|
|
5
|
+
export function claudeMd() {
|
|
6
|
+
return `<!-- ${GENERATED_MARK} — do not edit, правь AGENTS.md -->\n@AGENTS.md\n`;
|
|
7
|
+
}
|
|
8
|
+
export function skillMarker(name) {
|
|
9
|
+
return `${GENERATED_MARK} from .agents/skills/${name} — do not edit\n`;
|
|
10
|
+
}
|
|
11
|
+
export function claudeAgent(agent) {
|
|
12
|
+
const fields = { name: agent.name, description: agent.description };
|
|
13
|
+
if (agent.tools.length > 0)
|
|
14
|
+
fields['tools'] = agent.tools.join(', ');
|
|
15
|
+
if (agent.skills.length > 0)
|
|
16
|
+
fields['skills'] = agent.skills;
|
|
17
|
+
if (agent.model !== undefined)
|
|
18
|
+
fields['model'] = agent.model;
|
|
19
|
+
const yaml = stringify(fields, { lineWidth: 0 });
|
|
20
|
+
return `---\n# ${GENERATED_MARK} from ${agent.source} — do not edit\n${yaml}---\n\n${agent.instructions}\n`;
|
|
21
|
+
}
|
|
22
|
+
const tomlString = (value) => JSON.stringify(value);
|
|
23
|
+
function tomlMultiline(value) {
|
|
24
|
+
const escaped = value
|
|
25
|
+
.replace(/\\/g, '\\\\')
|
|
26
|
+
.replace(/"""/g, '""\\"')
|
|
27
|
+
.replace(/[^]/g, (c) => {
|
|
28
|
+
const code = c.charCodeAt(0);
|
|
29
|
+
const control = (code < 0x20 && code !== 0x09 && code !== 0x0a) || code === 0x7f;
|
|
30
|
+
return control ? `\\u${code.toString(16).padStart(4, '0')}` : c;
|
|
31
|
+
});
|
|
32
|
+
return `"""\n${escaped}\n"""`;
|
|
33
|
+
}
|
|
34
|
+
export function codexAgent(agent) {
|
|
35
|
+
const preface = agent.skills.length > 0 ? `Перед работой загрузи skills: ${agent.skills.join(', ')}.\n\n` : '';
|
|
36
|
+
return [
|
|
37
|
+
`# ${GENERATED_MARK} from ${agent.source} — do not edit`,
|
|
38
|
+
`name = ${tomlString(agent.name)}`,
|
|
39
|
+
`description = ${tomlString(agent.description)}`,
|
|
40
|
+
`developer_instructions = ${tomlMultiline(preface + agent.instructions)}`,
|
|
41
|
+
'',
|
|
42
|
+
].join('\n');
|
|
43
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Diagnostic } from '#shared/diagnostics';
|
|
2
|
+
import { type AgentDef } from '#lib/agent';
|
|
3
|
+
export type { AgentDef } from '#lib/agent';
|
|
4
|
+
export declare const AGENTS_MD = "AGENTS.md";
|
|
5
|
+
export declare const SKILLS_DIR = ".agents/skills";
|
|
6
|
+
export declare const AGENTS_DIR = ".agents/agents";
|
|
7
|
+
export interface SkillFile {
|
|
8
|
+
rel: string;
|
|
9
|
+
content: Buffer;
|
|
10
|
+
}
|
|
11
|
+
export interface Skill {
|
|
12
|
+
name: string;
|
|
13
|
+
files: SkillFile[];
|
|
14
|
+
}
|
|
15
|
+
export interface Sources {
|
|
16
|
+
hasAgentsMd: boolean;
|
|
17
|
+
skills: Skill[];
|
|
18
|
+
agents: AgentDef[];
|
|
19
|
+
diagnostics: Diagnostic[];
|
|
20
|
+
}
|
|
21
|
+
export declare function readSources(root: string): Promise<Sources>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { access, readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseAgent } from '#lib/agent';
|
|
4
|
+
import { errorCode } from '#shared/guards';
|
|
5
|
+
export const AGENTS_MD = 'AGENTS.md';
|
|
6
|
+
export const SKILLS_DIR = '.agents/skills';
|
|
7
|
+
export const AGENTS_DIR = '.agents/agents';
|
|
8
|
+
async function listDir(dir) {
|
|
9
|
+
try {
|
|
10
|
+
return (await readdir(dir)).sort();
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (errorCode(error) === 'ENOENT')
|
|
14
|
+
return [];
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function readSkill(root, name) {
|
|
19
|
+
const dir = path.join(root, SKILLS_DIR, name);
|
|
20
|
+
const entries = await readdir(dir, { recursive: true, withFileTypes: true });
|
|
21
|
+
const files = [];
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (!entry.isFile())
|
|
24
|
+
continue;
|
|
25
|
+
const abs = path.join(entry.parentPath, entry.name);
|
|
26
|
+
files.push({
|
|
27
|
+
rel: path.relative(dir, abs).split(path.sep).join('/'),
|
|
28
|
+
content: await readFile(abs),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
files.sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));
|
|
32
|
+
return { name, files };
|
|
33
|
+
}
|
|
34
|
+
export async function readSources(root) {
|
|
35
|
+
const diagnostics = [];
|
|
36
|
+
const hasAgentsMd = await access(path.join(root, AGENTS_MD)).then(() => true, () => false);
|
|
37
|
+
const skills = [];
|
|
38
|
+
for (const name of await listDir(path.join(root, SKILLS_DIR))) {
|
|
39
|
+
const skill = await readSkill(root, name).catch((error) => {
|
|
40
|
+
if (errorCode(error) === 'ENOTDIR')
|
|
41
|
+
return null;
|
|
42
|
+
throw error;
|
|
43
|
+
});
|
|
44
|
+
if (skill === null)
|
|
45
|
+
continue;
|
|
46
|
+
if (!skill.files.some((f) => f.rel === 'SKILL.md')) {
|
|
47
|
+
diagnostics.push({
|
|
48
|
+
file: `${SKILLS_DIR}/${name}`,
|
|
49
|
+
message: 'нет SKILL.md — не skill, пропущен',
|
|
50
|
+
});
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
skills.push(skill);
|
|
54
|
+
}
|
|
55
|
+
const agents = [];
|
|
56
|
+
for (const entry of await listDir(path.join(root, AGENTS_DIR))) {
|
|
57
|
+
if (!entry.endsWith('.md'))
|
|
58
|
+
continue;
|
|
59
|
+
const file = `${AGENTS_DIR}/${entry}`;
|
|
60
|
+
const report = (message) => {
|
|
61
|
+
diagnostics.push({ file, message });
|
|
62
|
+
};
|
|
63
|
+
const agent = parseAgent(file, await readFile(path.join(root, file), 'utf8'), report);
|
|
64
|
+
if (agent === null)
|
|
65
|
+
continue;
|
|
66
|
+
for (const skill of agent.skills) {
|
|
67
|
+
if (!skills.some((s) => s.name === skill))
|
|
68
|
+
report(`skill «${skill}» не найден в ${SKILLS_DIR}`);
|
|
69
|
+
}
|
|
70
|
+
agents.push(agent);
|
|
71
|
+
}
|
|
72
|
+
return { hasAgentsMd, skills, agents, diagnostics };
|
|
73
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AdapterName, HookKey } from '#shared/config';
|
|
2
|
+
import type { Diagnostic } from '#shared/diagnostics';
|
|
3
|
+
export interface Action {
|
|
4
|
+
op: 'write' | 'delete';
|
|
5
|
+
path: string;
|
|
6
|
+
content?: Buffer;
|
|
7
|
+
}
|
|
8
|
+
export interface SyncResult {
|
|
9
|
+
actions: Action[];
|
|
10
|
+
diagnostics: Diagnostic[];
|
|
11
|
+
}
|
|
12
|
+
export declare function computeSync(root: string, adapters: readonly AdapterName[], hooks?: readonly HookKey[]): Promise<SyncResult>;
|
|
13
|
+
export declare function applyActions(root: string, actions: Action[]): Promise<void>;
|
|
14
|
+
export declare function checkAdapters(root: string, adapters: readonly AdapterName[], hooks?: readonly HookKey[]): Promise<Diagnostic[]>;
|