@rsconcept/rstool 1.7.1 → 1.9.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.
Files changed (38) hide show
  1. package/README.md +5 -5
  2. package/dist/index.js +1 -1
  3. package/dist/mappers/model-adapter.d.ts +5 -0
  4. package/dist/mappers/model-adapter.js +28 -3
  5. package/dist/mappers/model-adapter.js.map +1 -1
  6. package/dist/mappers/schema-adapter.js +1 -1
  7. package/dist/models/index.js +1 -1
  8. package/dist/models/rstool-agent.js +1 -1
  9. package/dist/{models-DG2uTaV_.js → models-D06g8LId.js} +6 -5
  10. package/dist/{models-DG2uTaV_.js.map → models-D06g8LId.js.map} +1 -1
  11. package/dist/{rsform-builder-DKwZfJvN.js → rsform-builder-BlXEe5Ow.js} +2 -2
  12. package/dist/{rsform-builder-DKwZfJvN.js.map → rsform-builder-BlXEe5Ow.js.map} +1 -1
  13. package/dist/session/session-store.js +1 -1
  14. package/dist/wrapper/client.d.ts +12 -5
  15. package/dist/wrapper/client.js +79 -12
  16. package/dist/wrapper/client.js.map +1 -1
  17. package/dist/wrapper/stdio-wrapper.d.ts +1 -1
  18. package/dist/wrapper/stdio-wrapper.js +18 -10
  19. package/dist/wrapper/stdio-wrapper.js.map +1 -1
  20. package/docs/CONCEPTUAL-SCHEMA.md +1 -1
  21. package/docs/CONSTRUCTIONS.md +372 -0
  22. package/docs/DIAGNOSTICS.md +6 -6
  23. package/docs/MODEL-TESTING.md +48 -0
  24. package/docs/README.md +1 -0
  25. package/docs/SYNTAX.md +1 -1
  26. package/docs/TYPIFICATION.md +11 -2
  27. package/package.json +20 -19
  28. package/skills/README.md +1 -1
  29. package/skills/rstool-helper/EXAMPLES.md +24 -0
  30. package/skills/rstool-helper/GUIDE.md +46 -19
  31. package/skills/rstool-helper/REFERENCE.md +22 -3
  32. package/skills/rstool-helper/SKILL.md +6 -3
  33. package/src/mappers/model-adapter.ts +34 -1
  34. package/src/models/rstool-agent.test.ts +95 -0
  35. package/src/wrapper/client.test.ts +43 -4
  36. package/src/wrapper/client.ts +95 -16
  37. package/src/wrapper/stdio-handler.test.ts +43 -0
  38. package/src/wrapper/stdio-handler.ts +19 -5
@@ -18,7 +18,7 @@
18
18
  - Stdio/MCP: **плоские** `params` — поля метода и optional `sessionId` на одном уровне (без `params.input`).
19
19
  - Состояние: `getSessionState()` (summary) или `getSessionState('full')`.
20
20
  - Импорт/экспорт Portal: `importData`, `exportPortal({ kind })`.
21
- - Модель: `setModelValues`, `evaluate`, `recalculateModel`.
21
+ - Модель: `setModelValues`, `evaluate`, `recalculateModel`. Статусы вычисления приходят числами (`EvalStatus`, см. REFERENCE).
22
22
  - Перед сохранением сомнительной формулы — `analyzeExpression`.
23
23
 
24
24
  ## Задача → чтение
@@ -31,9 +31,10 @@
31
31
  | Оценить / отревьюить готовую КС (без запроса «перепиши») | этот GUIDE → [ревью / оценка](#ревью--оценка-готовой-кс); [чеклист ревью](#ревью-концептуальной-схемы); [CONCEPTUAL-SCHEMA.md](../../docs/CONCEPTUAL-SCHEMA.md); при импорте — [PORTAL-API.md](../../docs/PORTAL-API.md) | EXAMPLES с batch patch; полный SYNTAX |
32
32
  | Точечные правки существующей схемы | этот GUIDE → [редактирование](#редактирование-и-проверка-схемы); [REFERENCE.md](REFERENCE.md) при вызовах | CONCEPTUAL-SCHEMA целиком |
33
33
  | Починить формулу / диагностики | этот GUIDE → [цикл диагностик](#цикл-диагностик); [DIAGNOSTICS.md](../../docs/DIAGNOSTICS.md); [EXAMPLES.md](EXAMPLES.md) «типичные диагностики» | CONCEPTUAL-SCHEMA; PORTAL-API |
34
+ | Написать формулу; синтаксис `D{}`/`I{}`/`R{}`, `F#`/`P#` | [SYNTAX.md](../../docs/SYNTAX.md); при необходимости [EXAMPLES.md](EXAMPLES.md) | CONSTRUCTIONS; воркфлоу сессий |
35
+ | Объяснить формулу или конструкцию человеку | [CONSTRUCTIONS.md](../../docs/CONSTRUCTIONS.md) | SYNTAX целиком; воркфлоу сессий |
34
36
  | Импорт / правка по ссылке Portal | этот GUIDE → [Portal](#оценка-или-правка-кс-из-portal); [PORTAL-API.md](../../docs/PORTAL-API.md) | SYNTAX; BASE-SELECTION |
35
37
  | Проверить смысл на данных / КМ | этот GUIDE → [КМ](#проверка-на-маленькой-км); [MODEL-TESTING.md](../../docs/MODEL-TESTING.md) | DOMAIN/ОСС; CONCEPTUAL-SCHEMA |
36
- | Объяснить синтаксис / генераторы / `F#`/`P#` | [SYNTAX.md](../../docs/SYNTAX.md); при необходимости [EXAMPLES.md](EXAMPLES.md) | воркфлоу сессий |
37
38
  | Разобрать типизацию / ступени | [TYPIFICATION.md](../../docs/TYPIFICATION.md) | PORTAL-API |
38
39
  | Вызовы API / stdio / MCP | [REFERENCE.md](REFERENCE.md) | docs языка |
39
40
  | Термины КС, ядро/тело, не путать ярлыки | [DOMAIN.md](../../docs/DOMAIN.md) | — |
@@ -56,10 +57,28 @@
56
57
  Из корня workspace Portal (после `pnpm install` и сборки `@rsconcept/domain`):
57
58
 
58
59
  ```bash
59
- pnpm --filter @rsconcept/rstool exec tsx path/to/your/build-schema.ts
60
+ pnpm --filter @rsconcept/rstool exec tsx C:/path/to/review.mts
60
61
  ```
61
62
 
62
- В скрипте импортируй API пакета (`@rsconcept/rstool` или относительный путь к `rsconcept/rstool/src` только если скрипт лежит вне пакета и это осознанный выбор). Либо используй stdio/MCP без отдельного файла.
63
+ Правила для скрипта **вне** `rsconcept/rstool/`:
64
+
65
+ - `import … from '@rsconcept/rstool'` из такого файла **не резолвится** — пакет не поднят в корневой `node_modules`. Импортируй по абсолютному `file://` URL: `file:///<workspace>/rsconcept/rstool/src/index.ts` (или `…/dist/index.js`). На Windows абсолютный путь без `file://` тоже падает (`ERR_UNSUPPORTED_ESM_URL_SCHEME`).
66
+ - Расширение **`.mts`**: `.ts` вне пакета трактуется как CommonJS, а `@rsconcept/*` экспортируют только ESM (`ERR_PACKAGE_PATH_NOT_EXPORTED`).
67
+ - Helpers domain — тем же способом: `file:///…/rsconcept/domain/src/index.ts` (`TUPLE_ID`), `…/domain/src/rslang/labels.ts` (`labelType`).
68
+ - Вывод в файл с `-Encoding utf8` (PowerShell) — в консоли cp1251 символы ЯРЭ ломаются.
69
+
70
+ ```ts
71
+ import { readFileSync } from 'node:fs';
72
+ // <workspace> — абсолютный путь к корню Portal, например D:/work/Portal или /home/me/Portal
73
+ import { RSToolAgent } from 'file:///<workspace>/rsconcept/rstool/src/index.ts';
74
+ import { TUPLE_ID } from 'file:///<workspace>/rsconcept/domain/src/index.ts';
75
+
76
+ const tool = new RSToolAgent();
77
+ tool.importData(readFileSync('C:/tmp/details.json', 'utf-8'));
78
+ console.log(tool.listDiagnostics());
79
+ ```
80
+
81
+ Альтернатива без резолвинга — stdio-обёртка из собранного `dist`: `RSToolWrapperClient({ command: 'node', args: ['<abs>/rsconcept/rstool/dist/wrapper/stdio-wrapper.js'], cwd: '<abs>/rsconcept/rstool' })`. Внутри пакета (тесты, штатные `examples/`) обычный `@rsconcept/rstool` работает.
63
82
 
64
83
  ## Что читать
65
84
 
@@ -72,6 +91,7 @@ pnpm --filter @rsconcept/rstool exec tsx path/to/your/build-schema.ts
72
91
  - Концептуализация, создание схемы: [../../docs/CONCEPTUAL-SCHEMA.md](../../docs/CONCEPTUAL-SCHEMA.md).
73
92
  - Конституенты и валидация: [../../docs/CONSTITUENTA.md](../../docs/CONSTITUENTA.md).
74
93
  - Синтаксис ЯРЭ: [../../docs/SYNTAX.md](../../docs/SYNTAX.md).
94
+ - Объяснение формул и конструкций: [../../docs/CONSTRUCTIONS.md](../../docs/CONSTRUCTIONS.md).
75
95
  - Типизация: [../../docs/TYPIFICATION.md](../../docs/TYPIFICATION.md).
76
96
  - Проверка определений на КМ: [../../docs/MODEL-TESTING.md](../../docs/MODEL-TESTING.md).
77
97
  - Диагностики: [../../docs/DIAGNOSTICS.md](../../docs/DIAGNOSTICS.md).
@@ -130,19 +150,20 @@ kind `auto` (default) определит `portal-details` или `portal-schema`
130
150
  **Правило:** без явного запроса на правку не делай массовый рефакторинг и не заменяй схему «улучшенной версией». Сначала отчёт; правки — только по согласованию или если пользователь сразу сказал «исправь».
131
151
 
132
152
  1. Загрузи схему: текущая сессия, `importData`, или Portal → [PORTAL-API.md](../../docs/PORTAL-API.md).
133
- 2. `listDiagnostics()` — раздели `expression` / `schema` / `model`; зафиксируй ошибки и предупреждения.
134
- 3. `getSessionState('summary')` — обзор состава; при необходимости `full` для формул.
153
+ 2. `listDiagnostics()` — раздели `expression` / `schema` / `model`; зафиксируй ошибки и предупреждения. `analysisSuccess: true` в summary **не** означает отсутствие диагностик (например, `invalidPropertyUsage` даёт `error` при успешном разборе) — опирайся на `listDiagnostics`.
154
+ 3. `getSessionState('summary')` — обзор состава; при необходимости `full` для формул. Типизацию из `analysis.type` печатай через `labelType` ([TYPIFICATION.md](../../docs/TYPIFICATION.md)), а не разбирай AST вручную.
135
155
  4. Структурный проход по [чеклисту ревью](#ревью-концептуальной-схемы) и [CONCEPTUAL-SCHEMA.md](../../docs/CONCEPTUAL-SCHEMA.md):
136
156
  - неопределяемые vs производные (`S#` там, где должно быть `D#`/`F#`/`P#`);
137
157
  - `X#` / `C#` / `Z` ([BASE-SELECTION.md](../../docs/BASE-SELECTION.md));
138
158
  - терм-функции vs множества пар;
139
159
  - радикалы `R#` не объявлены конституентами;
140
160
  - покрытие задачи vs избыточность.
141
- 5. Severity в отчёте:
142
- - **блокер** — диагностики `error`, схема не анализируется / ломает вычисление;
143
- - **важно** — нарушение правил проектирования (лишнее `S#`, график вместо `F#`, смешение экспликаций);
144
- - **замечание** — стиль, именование, возможные упрощения, пробелы покрытия.
145
- 6. Ответ — по [шаблону ревью](#шаблон-ответа-ревью). Предлагай конкретные правки списком; применяй их только по запросу.
161
+ 5. Severity в отчёте — по **смыслу** диагностики, а не только по полю `severity` (инструмент помечает `error` и метаданные):
162
+ - **блокер** — `expression` с `severity: error` (формула не разбирается, невычислима: `invalidPropertyUsage`, …), `schemaDependencyCycle`, `schemaFormalDuplicate`, `schemaHomonym`; схема не анализируется / ломает вычисление;
163
+ - **важно** — нарушение правил проектирования (лишнее `S#`, график вместо `F#`, смешение экспликаций, перекрёстные ссылки `C#`, термин сильнее формулы);
164
+ - **замечание** — `schemaMissingConvention`, `schemaMissingTerm`, `modelEmpty` у структур, не задействованных в проверке; стиль, именование, упрощения, пробелы покрытия.
165
+ 6. Проверь на маленькой КМ ключевые производные и аксиомы ([цикл КМ](#проверка-на-маленькой-км)): позитивный набор данных → ожидаемые значения, негативный → `AXIOM_FALSE`. Это чаще всего даёт главные находки (расхождение термина и формулы, лишние/пропущенные элементы).
166
+ 7. Ответ — по [шаблону ревью](#шаблон-ответа-ревью). Предлагай конкретные правки списком; применяй их только по запросу.
146
167
 
147
168
  ### Цикл диагностик
148
169
 
@@ -160,12 +181,14 @@ kind `auto` (default) определит `portal-details` или `portal-schema`
160
181
 
161
182
  Когда синтаксис верен, но смысл формулы неочевиден. Подробности и форма данных — [MODEL-TESTING.md](../../docs/MODEL-TESTING.md).
162
183
 
163
- 1. Отдельная `createSession` или изолированная копия текущей сессии.
184
+ 1. Отдельная `createSession` или изолированная копия текущей сессии (для ревью схемы из Portal достаточно самой импортированной сессии).
164
185
  2. Только нужные поставщики: `X#`, `C#`, `S#` и проверяемые `D#` / `F#` / `P#` / `A#`.
165
186
  3. `analyzeExpression` и `applySchemaPatch`.
166
- 4. Значения: `setModelValues`.
167
- 5. `evaluate`; сравни с ожидаемым `value`.
168
- 6. При нескольких зависимых определениях — `recalculateModel`.
187
+ 4. Значения: `setModelValues`. `X#` — `{ 0: 'a', 1: 'b' }` (индекс → подпись), `S#` — массив кортежей `[TUPLE_ID, i, j]` по индексам `X#`; порядок элементов не важен, значения приводятся к канонической форме автоматически.
188
+ 5. `evaluate({ constituentId })` для конституенты; `evaluate({ expression, cstType })` для scratch-выражения — `cstType` задаёт ожидаемую ступень: множества и числа — `'term'`, логические выражения (`P1[S1]`, `D2⊆S1`) — `'axiom'`. В scratch можно ссылаться на любые конституенты схемы, включая `D#` — зависимости вычисляются автоматически.
189
+ 6. `status` в ответе — **число** (`EvalStatus`): `7` `HAS_DATA`, `6` `EMPTY`, `5` `AXIOM_FALSE`, `4` `EVAL_FAIL`, `3` `INVALID_DATA`, `1` `NO_EVAL`; полный список — [REFERENCE.md](REFERENCE.md). Сравни `value` с ожидаемым.
190
+ 7. При нескольких зависимых определениях — `recalculateModel`.
191
+ 8. Для аксиом — два набора данных: корректный (истина) и нарушающий (`AXIOM_FALSE`).
169
192
 
170
193
  Для регрессий вынеси проверку в скрипт или colocated `*.test.ts` (см. [MODEL-TESTING.md](../../docs/MODEL-TESTING.md)).
171
194
 
@@ -194,8 +217,8 @@ kind `auto` (default) определит `portal-details` или `portal-schema`
194
217
  2. **Диагностики:** число ошибок / предупреждений по `expression` / `schema` / `model` (без полного JSON).
195
218
  3. **Находки** списком: блокер → важно → замечание; у каждой — имя конституенты и суть.
196
219
  4. **Покрытие:** что из задачи отражено; чего не хватает.
197
- 5. **Рекомендации:** конкретные правки; не применяй их без запроса.
198
- 6. **Проверки:** что уже сделано (import, listDiagnostics, выборочный анализ).
220
+ 5. **Рекомендации:** конкретные правки; не применяй их без запроса. Предложенную замену формулы прогони через анализ выражения (и по возможности через КМ) до того, как её советовать.
221
+ 6. **Проверки:** что уже сделано (импорт, диагностики, выборочный анализ) и **КМ**: данные → ожидание → результат для проверенных конституент.
199
222
 
200
223
  ### Шаблон ответа: диагностики
201
224
 
@@ -206,10 +229,14 @@ kind `auto` (default) определит `portal-details` или `portal-schema`
206
229
 
207
230
  ### Шаблон ответа: объяснение формулы
208
231
 
232
+ Полный сценарий и карточки конструкций — [CONSTRUCTIONS.md](../../docs/CONSTRUCTIONS.md). Кратко:
233
+
209
234
  - роль конституенты и термин;
210
235
  - формула как есть;
211
- - по шагам: что вычисляется / утверждается;
212
- - на какие понятия опирается; типизация.
236
+ - ТМВ или ЛВ;
237
+ - снаружи внутрь, только конструкции из формулы;
238
+ - ступень и опоры;
239
+ - вычисление на данных — только по запросу.
213
240
 
214
241
  ### Язык ответа
215
242
 
@@ -114,7 +114,9 @@ tool.listDiagnostics({ kind: 'expression' });
114
114
  tool.listDiagnostics({ kind: 'schema', constituentId: 3, severity: 'error' });
115
115
  ```
116
116
 
117
- Фильтры: `kind?: 'expression' | 'schema' | 'model'`, `constituentId?`, `severity?: 'error' | 'warning'`.
117
+ Фильтры: `kind?: 'expression' | 'schema' | 'model'`, `constituentId?`, `severity?: 'error' | 'warning'`. В stdio / MCP те же поля передаются **плоско** в `params` (`{ "kind": "schema", "severity": "warning" }`); stdio дополнительно принимает устаревший вложенный `filters`.
118
+
119
+ `severity` отражает правило инструмента, а не вес для ревью: `schemaMissingConvention` / `schemaMissingTerm` приходят как `error`. Как ранжировать в отчёте — [GUIDE.md](GUIDE.md), «Ревью / оценка готовой КС».
118
120
 
119
121
  Элемент диагностики (агентский вид): `kind`, `code`, `name`, `severity`, `alias?`, `expression`, `from`, `to`, `params?`, `stack?` (цепочка вызовов при ошибке внутри `F#`/`P#`). Ключ для поиска исправления — `name` ([DIAGNOSTICS.md](../../docs/DIAGNOSTICS.md)).
120
122
 
@@ -132,6 +134,8 @@ tool.analyzeExpression({
132
134
 
133
135
  **Результат:** `{ success, type, valueClass, diagnostics }`. Не сохраняет конституенту. Для черновика формулы — до `applySchemaPatch`.
134
136
 
137
+ `type` — AST типизации; для чтения используй `labelType` из `@rsconcept/domain/rslang/labels` (`ℬ(X1×X1)`, `[X1] → ℬ(X1)`, `Logic`). `success: true` совместим с диагностикой `error` (например, `invalidPropertyUsage` при `valueClass: null`) — проверяй `diagnostics`.
138
+
135
139
  ## `evaluate`
136
140
 
137
141
  Ровно один вариант входа:
@@ -141,7 +145,19 @@ tool.analyzeExpression({
141
145
 
142
146
  **Результат:** `{ success, value, status, iterations, cacheHits, diagnostics }`.
143
147
 
144
- `status` — статус вычисления КМ (`HAS_DATA`, `EMPTY`, `AXIOM_FALSE`, `EVAL_FAIL`, … — [DOMAIN.md](../../docs/DOMAIN.md)).
148
+ `status` — статус вычисления КМ, **числовой** код `EvalStatus` (`@rsconcept/domain`); смысл — [DOMAIN.md](../../docs/DOMAIN.md):
149
+
150
+ | Код | Имя | Смысл |
151
+ | --: | --------------- | --------------------------------------- |
152
+ | `1` | `NO_EVAL` | не вычисляется (`X#`, `C#`, `F#`, `P#`) |
153
+ | `2` | `NOT_PROCESSED` | вычисление не запускалось |
154
+ | `3` | `INVALID_DATA` | неверная интерпретация |
155
+ | `4` | `EVAL_FAIL` | ошибка вычисления |
156
+ | `5` | `AXIOM_FALSE` | аксиома ложна |
157
+ | `6` | `EMPTY` | результат пуст |
158
+ | `7` | `HAS_DATA` | вычислено, непусто |
159
+
160
+ Scratch-выражение: `cstType` задаёт ожидаемую ступень (`term` — множество / число, `axiom` — логическое). Ссылки на `D#` / `A#` / `T#` внутри scratch допустимы — их значения (и зависимости через `F#` / `P#`) вычисляются перед оценкой.
145
161
 
146
162
  ## `setModelValues` / `getModelState` / `recalculateModel`
147
163
 
@@ -155,8 +171,9 @@ await tool.setModelValues({
155
171
  - `set[]`: `{ target, value, type? }` — `target` = id конституенты; `type` обычно не нужен.
156
172
  - `clear[]`: id для сброса значений.
157
173
  - Метод **async**. После изменения данных пересчитываются диагностики модели.
174
+ - Значения `S#` приводятся к канонической форме (сортировка, без дубликатов) — порядок элементов во входе не важен; в `getModelState` хранится уже нормализованное значение.
158
175
  - `getModelState()` → `{ items: [{ id, type, value }] }`.
159
- - `recalculateModel()` → `{ items: [{ id, alias, value, status }] }`.
176
+ - `recalculateModel()` → `{ items: [{ id, alias, value, status }] }` (`status` — числовой `EvalStatus`, таблица выше).
160
177
 
161
178
  Формы `value` для `X#` / `S#` — [MODEL-TESTING.md](../../docs/MODEL-TESTING.md).
162
179
 
@@ -237,6 +254,7 @@ JSON-строка сессии для последующего `importData(...,
237
254
  {"id":"9","method":"exportPortal","params":{"kind":"schema"}}
238
255
  {"id":"10","method":"importData","params":{"payload":"..."}}
239
256
  {"id":"11","method":"getSessionState","params":{"detail":"summary"}}
257
+ {"id":"12","method":"listDiagnostics","params":{"kind":"expression","severity":"error"}}
240
258
  ```
241
259
 
242
260
  ## Help map: ошибка → документ
@@ -245,6 +263,7 @@ JSON-строка сессии для последующего `importData(...,
245
263
  | -------------------------------------------------- | ------------------------------------------------------- |
246
264
  | Код / `name` диагностики | [DIAGNOSTICS.md](../../docs/DIAGNOSTICS.md) |
247
265
  | Синтаксис операторов, `D{}`/`I{}`/`R{}`, `F#`/`P#` | [SYNTAX.md](../../docs/SYNTAX.md) |
266
+ | Объяснить формулу / конструкцию человеку | [CONSTRUCTIONS.md](../../docs/CONSTRUCTIONS.md) |
248
267
  | Ступени, `AnalysisResult.type` | [TYPIFICATION.md](../../docs/TYPIFICATION.md) |
249
268
  | `X#` vs `C#` vs `Z`, переносимость | [BASE-SELECTION.md](../../docs/BASE-SELECTION.md) |
250
269
  | Проектирование, `F#` vs пары, ревью | [CONCEPTUAL-SCHEMA.md](../../docs/CONCEPTUAL-SCHEMA.md) |
@@ -2,8 +2,9 @@
2
2
  name: rstool-helper
3
3
  description: >-
4
4
  ЯРЭ и концептуальные схемы через rstool: концептуализация, ревью/оценка КС,
5
- правки и диагностики формул, проверка на КМ (evaluate), выбор X#/C#/Z,
6
- импорт/экспорт Portal (/rsforms, /details). Читай GUIDE и docs из node_modules.
5
+ правки и диагностики формул, объяснение конструкций ЯРЭ, проверка на КМ (evaluate),
6
+ выбор X#/C#/Z, импорт/экспорт Portal (/rsforms, /details).
7
+ Читай GUIDE и docs из node_modules.
7
8
  ---
8
9
 
9
10
  # rstool-helper
@@ -39,9 +40,11 @@ description: >-
39
40
  | Собрать / развить КС из текста | GUIDE: концептуализация; `docs/CONCEPTUAL-SCHEMA.md`; `docs/BASE-SELECTION.md` |
40
41
  | Оценить / отревьюить готовую КС | GUIDE: ревью; чеклист ревью; `docs/CONCEPTUAL-SCHEMA.md` |
41
42
  | Починить формулу / диагностики | GUIDE: цикл диагностик; `docs/DIAGNOSTICS.md` |
43
+ | Написать формулу / синтаксис | `docs/SYNTAX.md` |
44
+ | Объяснить формулу / конструкцию | `docs/CONSTRUCTIONS.md` |
42
45
  | Импорт / правка по ссылке Portal | GUIDE: Portal; `docs/PORTAL-API.md` |
43
46
  | Проверить смысл на данных | GUIDE: КМ; `docs/MODEL-TESTING.md` |
44
- | Синтаксис / типизация / вызов API | `docs/SYNTAX.md` / `TYPIFICATION.md` / `REFERENCE.md` |
47
+ | Типизация / вызов API | `docs/TYPIFICATION.md` / `REFERENCE.md` |
45
48
 
46
49
  Перед работой всегда открой **GUIDE.md**; остальное — по строке таблицы.
47
50
 
@@ -1,5 +1,7 @@
1
1
  import { DiagnosticKind, expressionDiagnostic } from './diagnostic-assembly';
2
2
  import { type RSErrorDescription, type Value } from '@rsconcept/domain/rslang';
3
+ import { extractGlobals } from '@rsconcept/domain/rslang/api';
4
+ import { normalizeValue } from '@rsconcept/domain/rslang/eval/value-api';
3
5
  import { isBaseSet } from '@rsconcept/domain/library/rsform-api';
4
6
  import { CstType, type RSForm } from '@rsconcept/domain/library/rsform';
5
7
  import { RSEngine, type RSEngineServices } from '@rsconcept/domain/library/rsengine';
@@ -37,7 +39,11 @@ export class ModelAdapter {
37
39
  const binding = toBasicBinding(input.value as Record<string | number, string>);
38
40
  await engine.setBasicValue(input.target, binding);
39
41
  } else {
40
- await engine.setStructureValue(input.target, input.value as Value);
42
+ // Set operations in the evaluator assume canonical form (sorted, deduplicated);
43
+ // agents may pass elements in arbitrary order.
44
+ const value = structuredClone(input.value) as Value;
45
+ normalizeValue(value);
46
+ await engine.setStructureValue(input.target, value);
41
47
  }
42
48
  session.updatedAt = new Date().toISOString();
43
49
  return structuredClone(session.model);
@@ -64,6 +70,7 @@ export class ModelAdapter {
64
70
 
65
71
  public evaluateExpression(session: SessionState, expression: string, cstType: CstType): EvaluationResult {
66
72
  const engine = this.createEngine(session);
73
+ this.prepareReferencedValues(engine, session, expression);
67
74
  const result = engine.evaluateExpression(expression, cstType);
68
75
  const status =
69
76
  result.value === null
@@ -120,6 +127,32 @@ export class ModelAdapter {
120
127
  return engine;
121
128
  }
122
129
 
130
+ /**
131
+ * Computes values of inferrable constituents referenced by a scratch expression (directly or
132
+ * through `F#` / `P#` bodies). A fresh engine only holds interpreted `X#` / `C#` / `S#` values.
133
+ */
134
+ private prepareReferencedValues(engine: RSEngine, session: SessionState, expression: string): void {
135
+ const schema = engine.schema;
136
+ if (!schema) {
137
+ return;
138
+ }
139
+ const globals = extractGlobals(expression);
140
+ const referenced = session.items.filter(item => globals.has(item.alias)).map(item => item.id);
141
+ if (referenced.length === 0) {
142
+ return;
143
+ }
144
+ const required = new Set([...referenced, ...schema.graph.expandAllInputs(referenced)]);
145
+ for (const cstID of schema.graph.topologicalOrder()) {
146
+ if (!required.has(cstID)) {
147
+ continue;
148
+ }
149
+ const cst = schema.cstByID.get(cstID);
150
+ if (cst && isInferrable(cst.cst_type)) {
151
+ engine.calculateCst(cstID);
152
+ }
153
+ }
154
+ }
155
+
123
156
  private validateSetInput(session: SessionState, input: SetConstituentaValueInput): void {
124
157
  const cst = session.items.find(item => item.id === input.target);
125
158
  if (!cst) {
@@ -1029,6 +1029,101 @@ describe('RSToolAgent modeling semantics', () => {
1029
1029
  expect(modelIssues.some(record => record.code === RSDiagnosticCode.modelAxiomFalse)).toBe(true);
1030
1030
  });
1031
1031
 
1032
+ function buildGraphScratch(tool: RSToolAgent, sessionId: string) {
1033
+ tool.applySchemaPatch(
1034
+ {
1035
+ items: [
1036
+ { alias: 'X1' },
1037
+ { alias: 'S1', definitionFormal: 'ℬ(X1×X1)' },
1038
+ { alias: 'D1', definitionFormal: 'Pr2,1(S1)' },
1039
+ { alias: 'D2', definitionFormal: 'S1\\D1' },
1040
+ { alias: 'F1', definitionFormal: '[α∈X1] Pr2(Fi1[{α}](D2))' }
1041
+ ]
1042
+ },
1043
+ sessionId
1044
+ );
1045
+ }
1046
+
1047
+ it('normalizes unsorted structure values before evaluation', async () => {
1048
+ const tool = new RSToolAgent();
1049
+ const session = tool.createSession();
1050
+ buildGraphScratch(tool, session.sessionId);
1051
+
1052
+ await tool.setModelValues(
1053
+ {
1054
+ set: [
1055
+ { target: 1, value: { 0: 'a', 1: 'b', 2: 'c' } },
1056
+ {
1057
+ target: 2,
1058
+ value: [
1059
+ [TUPLE_ID, 1, 2],
1060
+ [TUPLE_ID, 0, 2],
1061
+ [TUPLE_ID, 0, 1],
1062
+ [TUPLE_ID, 1, 0],
1063
+ [TUPLE_ID, 0, 1]
1064
+ ]
1065
+ }
1066
+ ]
1067
+ },
1068
+ session.sessionId
1069
+ );
1070
+
1071
+ const stored = tool.getModelState(session.sessionId).items.find(item => item.id === 2);
1072
+ expect(stored?.value).toEqual([
1073
+ [TUPLE_ID, 0, 1],
1074
+ [TUPLE_ID, 0, 2],
1075
+ [TUPLE_ID, 1, 0],
1076
+ [TUPLE_ID, 1, 2]
1077
+ ]);
1078
+
1079
+ // S1 \ inverse(S1): (0,1) and (1,0) cancel out
1080
+ const result = tool.evaluate({ constituentId: 4 }, session.sessionId);
1081
+ expect(result.status).toBe(EvalStatus.HAS_DATA);
1082
+ expect(result.value).toEqual([
1083
+ [TUPLE_ID, 0, 2],
1084
+ [TUPLE_ID, 1, 2]
1085
+ ]);
1086
+ });
1087
+
1088
+ it('evaluates scratch expressions that reference derived constituents', async () => {
1089
+ const tool = new RSToolAgent();
1090
+ const session = tool.createSession();
1091
+ buildGraphScratch(tool, session.sessionId);
1092
+ await tool.setModelValues(
1093
+ {
1094
+ set: [
1095
+ { target: 1, value: { 0: 'a', 1: 'b', 2: 'c' } },
1096
+ {
1097
+ target: 2,
1098
+ value: [
1099
+ [TUPLE_ID, 0, 1],
1100
+ [TUPLE_ID, 0, 2],
1101
+ [TUPLE_ID, 1, 0],
1102
+ [TUPLE_ID, 1, 2]
1103
+ ]
1104
+ }
1105
+ ]
1106
+ },
1107
+ session.sessionId
1108
+ );
1109
+
1110
+ const direct = tool.evaluate({ expression: 'card(D2)', cstType: CstType.TERM }, session.sessionId);
1111
+ expect(direct.success).toBe(true);
1112
+ expect(direct.value).toBe(2);
1113
+
1114
+ // F1 body references D2, which must be computed before the call
1115
+ const viaFunction = tool.evaluate(
1116
+ { expression: 'card(red(I{F1[ξ] | ξ:∈Pr1(D2)}))', cstType: CstType.TERM },
1117
+ session.sessionId
1118
+ );
1119
+ expect(viaFunction.success).toBe(true);
1120
+ expect(viaFunction.value).toBe(1);
1121
+
1122
+ const logic = tool.evaluate({ expression: 'D2⊆S1', cstType: CstType.AXIOM }, session.sessionId);
1123
+ expect(logic.success).toBe(true);
1124
+ expect(logic.value).toBe(1);
1125
+ });
1126
+
1032
1127
  it('throws when evaluate input is incomplete', () => {
1033
1128
  const tool = new RSToolAgent();
1034
1129
  const session = tool.createSession();
@@ -5,14 +5,53 @@ import { describe, expect, it } from 'vitest';
5
5
  import { CstType, EvalStatus, RSToolWrapperClient } from '../index';
6
6
 
7
7
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
8
+ const tsxCli = fileURLToPath(import.meta.resolve('tsx/cli'));
9
+ const wrapperEntry = resolve(packageRoot, 'src/wrapper/stdio-wrapper.ts');
8
10
 
9
11
  describe('RSToolWrapperClient integration', () => {
12
+ it('rejects readiness and calls when the wrapper command cannot start', async () => {
13
+ const client = new RSToolWrapperClient({
14
+ command: resolve(packageRoot, 'definitely-missing-rstool-wrapper-binary'),
15
+ args: [],
16
+ cwd: packageRoot
17
+ });
18
+ try {
19
+ await expect(client.waitUntilReady()).rejects.toThrow(/ENOENT/);
20
+ await expect(client.call('methods')).rejects.toThrow(/ENOENT/);
21
+ } finally {
22
+ await client.close();
23
+ }
24
+ }, 30_000);
25
+
26
+ it('rejects outstanding calls when the wrapper exits before responding', async () => {
27
+ const client = new RSToolWrapperClient({
28
+ command: process.execPath,
29
+ args: ['-e', 'process.stdin.resume(); setTimeout(() => process.exit(3), 200)'],
30
+ cwd: packageRoot
31
+ });
32
+ try {
33
+ await expect(client.call('methods')).rejects.toThrow(/exited before responding \(code 3/);
34
+ } finally {
35
+ await client.close();
36
+ }
37
+ }, 30_000);
38
+
39
+ it('starts the default npm wrapper without a shell', async () => {
40
+ const client = new RSToolWrapperClient({ cwd: packageRoot });
41
+ try {
42
+ await client.waitUntilReady();
43
+ const methods = await client.call<string[]>('methods');
44
+ expect(methods).toContain('applySchemaPatch');
45
+ } finally {
46
+ await client.close();
47
+ }
48
+ }, 30_000);
49
+
10
50
  it('runs createSession, patch, model, and evaluate over stdio', async () => {
11
51
  const client = new RSToolWrapperClient({
12
- command: 'npx',
13
- args: ['tsx', resolve(packageRoot, 'src/wrapper/stdio-wrapper.ts')],
14
- cwd: packageRoot,
15
- shell: true
52
+ command: process.execPath,
53
+ args: [tsxCli, wrapperEntry],
54
+ cwd: packageRoot
16
55
  });
17
56
 
18
57
  try {