dsh-logicprobe 0.7.0 → 0.8.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/lib/client.js ADDED
@@ -0,0 +1,212 @@
1
+ /**
2
+ * logicprobe — browser half: the gate-injection switch on the dsh Web client's
3
+ * Plugins page.
4
+ *
5
+ * The Plugins page (`@deepseek-ai/dsh-client-ui-plugin-manager`) owns the
6
+ * sidebar **Plugins** entry and declares the slots a bundle's own configuration
7
+ * registers into. This module contributes one `plugins.bundle.config` entry,
8
+ * keyed by this package's npm name, so the switch renders on logicprobe's own
9
+ * page between its description and its rows.
10
+ *
11
+ * Why the switch writes through `configForms` rather than reaching for the
12
+ * profile file: dsh's settings service exposes only the Config fields declared
13
+ * `.volatile()`, and it rejects a write to any other path. `enabled` is such a
14
+ * field (see `src/index.ts`), so flipping the switch is an ordinary
15
+ * revision-fenced settings write that the running Host picks up in place — the
16
+ * injection there re-reads the reference on every model step.
17
+ *
18
+ * Shape: this is a prebuilt module-system bundle, not a source module. It calls
19
+ * `window.__ModuleLoader__.load({ id, factory })` with this package's resolved
20
+ * npm name, and `factory` returns the cordis plugin face. Only the client
21
+ * baseline is requested (`react` and
22
+ * `@deepseek-ai/dsh-client-ui-primitives`); every other capability arrives
23
+ * through cordis `inject`. `scripts/build-client.mjs` publishes this file
24
+ * verbatim as `lib/client.js`.
25
+ *
26
+ * @module dsh-logicprobe/client
27
+ */
28
+
29
+ window.__ModuleLoader__.load({
30
+ id: 'dsh-logicprobe',
31
+ factory: (require) => {
32
+ const React = require('react')
33
+ const { Button, Switch } = require('@deepseek-ai/dsh-client-ui-primitives')
34
+
35
+ /** Settings namespace: the Loader entry id this bundle's patch declares. */
36
+ const NS = 'logicprobe'
37
+ /** `plugins.bundle.config` key: the bundle's npm package name. */
38
+ const PACKAGE = 'dsh-logicprobe'
39
+ /** This page's dictionary namespace. */
40
+ const LOCALE_NS = 'logicprobe.plugins'
41
+ /** The Config field the switch writes inside the namespace's section. */
42
+ const FIELD = 'enabled'
43
+
44
+ /** English copy. */
45
+ const en = {
46
+ title: 'Gate injection',
47
+ label: 'Inject the gate text',
48
+ hint: 'Folds the claim-verification doctrine into the first model step of every session. Turning it off leaves the skills and the verification tools registered — only the injected text is dropped.',
49
+ overridden: 'Overridden',
50
+ reset: 'Reset to default',
51
+ readOnly: 'This deployment stores settings read-only.',
52
+ unavailable: 'This plugin is not loaded, so it cannot be configured right now.',
53
+ saveFailed: 'The deployment did not accept that value; the switch shows what is stored.',
54
+ }
55
+ /** Simplified Chinese copy. */
56
+ const zh = {
57
+ title: 'Gate 注入',
58
+ label: '注入 gate 文本',
59
+ hint: '把 claim 核查铁律折进每个会话的第一个模型步。关掉后 skills 与验证工具仍然注册,只是不再注入那段提示文本。',
60
+ overridden: '已覆盖',
61
+ reset: '恢复默认',
62
+ readOnly: '本部署的设置为只读。',
63
+ unavailable: '该插件当前未加载,暂时无法配置。',
64
+ saveFailed: '本部署没有接受这个值,开关显示的是已存下的状态。',
65
+ }
66
+
67
+ /** Required cordis services. */
68
+ const inject = ['slots', 'locale', 'configForms']
69
+
70
+ const GROUP = { display: 'flex', flexDirection: 'column', gap: '8px' }
71
+ const TITLE = { margin: 0, fontSize: '14px', fontWeight: '500', lineHeight: '22px' }
72
+ const ROW = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '16px' }
73
+ const LABEL = { fontSize: '13px', lineHeight: '20px' }
74
+ const NOTE = { margin: 0, fontSize: '12px', lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' }
75
+ const FAILED = { margin: 0, fontSize: '12px', lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' }
76
+
77
+ /**
78
+ * Whether a settings-layer value carries this field, which is what marks it
79
+ * overridden: an override equal to the default is still an override.
80
+ * @param layer - the raw user layer the form snapshot carries.
81
+ * @returns whether the layer holds the field.
82
+ */
83
+ function carries(layer) {
84
+ return layer !== null && typeof layer === 'object' && Object.prototype.hasOwnProperty.call(layer, FIELD)
85
+ }
86
+
87
+ /**
88
+ * Render the gate-injection switch, or the note saying why it cannot render.
89
+ * @param props - the page's `t` seat, the bound form snapshot hook, and the write actions.
90
+ * @returns the body of this bundle's configuration section.
91
+ */
92
+ function InjectionCard(props) {
93
+ const t = props.t
94
+ const state = props.useInjectionForm((snapshot) => snapshot)
95
+ const [pending, setPending] = React.useState(false)
96
+ const [failed, setFailed] = React.useState(false)
97
+
98
+ /** Run one settings write and report a refusal or a transport failure. */
99
+ const write = (run) => {
100
+ setPending(true)
101
+ setFailed(false)
102
+ Promise.resolve(run()).then(
103
+ (accepted) => {
104
+ setPending(false)
105
+ setFailed(accepted === false)
106
+ },
107
+ () => {
108
+ setPending(false)
109
+ setFailed(true)
110
+ },
111
+ )
112
+ }
113
+
114
+ if (state.status !== 'ready') {
115
+ return React.createElement('p', { style: NOTE }, t('unavailable'))
116
+ }
117
+
118
+ const section = state.value !== null && typeof state.value === 'object' ? state.value : {}
119
+ // The schema default is `true`; only an explicit false means off.
120
+ const checked = section[FIELD] !== false
121
+ const overridden = carries(state.user)
122
+ const locked = state.writable !== true || pending
123
+
124
+ const children = [
125
+ React.createElement('h4', { key: 'title', style: TITLE }, t('title')),
126
+ React.createElement('div', { key: 'row', style: ROW }, [
127
+ React.createElement('span', { key: 'label', style: LABEL }, t('label')),
128
+ React.createElement(Switch, {
129
+ key: 'switch',
130
+ checked,
131
+ disabled: locked,
132
+ label: t('label'),
133
+ onChange: (next) => write(() => props.setEnabled(next)),
134
+ }),
135
+ ]),
136
+ React.createElement('p', { key: 'hint', style: NOTE }, state.writable === true ? t('hint') : t('readOnly')),
137
+ ]
138
+
139
+ if (overridden) {
140
+ children.push(
141
+ React.createElement('div', { key: 'overridden', style: ROW }, [
142
+ React.createElement('span', { key: 'badge', style: NOTE }, t('overridden')),
143
+ React.createElement(
144
+ Button,
145
+ {
146
+ key: 'reset',
147
+ variant: 'outline',
148
+ size: 'sm',
149
+ disabled: locked,
150
+ onClick: () => write(() => props.resetEnabled()),
151
+ },
152
+ t('reset'),
153
+ ),
154
+ ]),
155
+ )
156
+ }
157
+
158
+ if (failed) {
159
+ children.push(React.createElement('p', { key: 'failed', style: FAILED, role: 'alert' }, t('saveFailed')))
160
+ }
161
+
162
+ return React.createElement('div', { style: GROUP }, children)
163
+ }
164
+
165
+ /**
166
+ * Mount the switch while the Host serves logicprobe's settings namespace.
167
+ * @param ctx - the browser plugin context.
168
+ */
169
+ function apply(ctx) {
170
+ ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), 'dsh-logicprobe: dictionaries')
171
+ // `whileServed` is the registration barrier that keeps this page alive only
172
+ // while the Host serves the namespace. Hosts predating it (measured: dsh
173
+ // 0.1.5-rc.3 and 0.1.6-alpha.2) have no live settings field to offer at all,
174
+ // so there is nothing to register — and calling it there would throw during
175
+ // this plugin's own activation, which the Web boot audit then reports as a
176
+ // failed client entry. Degrade to no page instead.
177
+ if (typeof ctx.configForms.whileServed !== 'function') return
178
+ // The page renders the section only for a bundle whose package name is in
179
+ // its configuration ledger, and the ledger follows this registration. The
180
+ // registration in turn waits for the namespace to be served, so a profile
181
+ // whose logicprobe row is switched off shows no trace of the switch.
182
+ ctx.effect(
183
+ () =>
184
+ ctx.configForms.whileServed([NS], () => {
185
+ const form = ctx.configForms.get(NS)
186
+ const source = {
187
+ getSnapshot: () => form.getSnapshot(),
188
+ subscribe: (listener) => form.subscribe(listener),
189
+ }
190
+ return ctx.slots.inject('plugins.bundle.config', () =>
191
+ ctx.slots.register(
192
+ {
193
+ name: 'plugins.bundle.config',
194
+ key: PACKAGE,
195
+ locale: LOCALE_NS,
196
+ inject: () => ({
197
+ hooks: { injectionForm: source },
198
+ setEnabled: (next) => form.set(FIELD, next),
199
+ resetEnabled: () => form.unset(FIELD),
200
+ }),
201
+ },
202
+ InjectionCard,
203
+ ),
204
+ )
205
+ }),
206
+ 'dsh-logicprobe: gate-injection switch',
207
+ )
208
+ }
209
+
210
+ return { inject, apply }
211
+ },
212
+ })
package/lib/index.js CHANGED
@@ -33,6 +33,7 @@ import { logicProbeDataModelVerifyTool, DATA_ENGINE_SCHEMA_VERSION } from './dat
33
33
  import { logicProbeConcurrencyScanTool } from './concurrency-tool.js';
34
34
  import { logicProbeComposeTool } from './compose-tool.js';
35
35
  import { logicProbeExportTool } from './export-tool.js';
36
+ import { logicProbeUmlTool } from './uml-tool.js';
36
37
  import { ENGINE_SCHEMA_VERSION } from './engine.js';
37
38
  export const name = 'logicprobe';
38
39
  // Skills are contributed through the registry service, which dsh-base always
@@ -59,15 +60,47 @@ Plugin logicprobe is active. Documents are not truth — code is. Verify every v
59
60
  | "I'll verify while implementing" | Verification happens before implementation, not during. |
60
61
  | "I can check this with reasoning alone" | Behavioral claims are verified with code/models, not intuition. One counter-example refutes a universal claim. |
61
62
 
62
- **Native verification path**: In dsh, prefer the \`logicprobe_verify\` tool for state-machine checks and \`logicprobe_datamodel_verify\` for data-model/schema migration checks. Both support before/after regression and common domain constraints (idempotency, monotonic, sequence, leads-to, atomicity). Python harnesses remain the fallback for non-dsh hosts.
63
+ **Native verification path**: In dsh, prefer the \`logicprobe_verify\` tool for state-machine checks and \`logicprobe_datamodel_verify\` for data-model/schema migration checks. Both support before/after regression and common domain constraints (idempotency, monotonic, sequence, leads-to, atomicity). To model a code flow as UML, or to audit such a modelling, use \`logicprobe_uml\` (render | parse | review). Python harnesses remain the fallback for non-dsh hosts.
63
64
 
64
65
  **Proactive suggestion**: When a user asks code-level behavioral questions — "could this state machine deadlock", "is this retry limit safe", "check this timing sequence for bugs", "is this migration non-breaking", "does this copy cover all required fields" — suggest logicprobe as an optional verification pass (do not auto-escalate).
65
66
  </EXTREMELY_IMPORTANT>`;
67
+ /**
68
+ * Declare a field as live where this host's schemastery can — `.volatile()`
69
+ * arrived in 3.18.3 — and leave it an ordinary field where it cannot.
70
+ *
71
+ * The fallback is load-bearing, not defensive padding. `Config` below is built
72
+ * while this module is still being evaluated, so an unconditional `.volatile()`
73
+ * on a host shipping schemastery 3.18.2 (measured: dsh 0.1.5-rc.2 and
74
+ * 0.1.5-rc.3) throws during import; the loader entry then fails and takes the
75
+ * WHOLE plugin tree — and the host's boot — down with it. Degrading costs only
76
+ * the Plugins-page switch, because the settings service projects nothing but
77
+ * fields under a `.volatile()` node; skills, tools and the gate injection are
78
+ * untouched. The returned schema keeps the plain field's static type; the
79
+ * `Config` interface below carries the union the host actually hands over.
80
+ */
81
+ function live(field) {
82
+ const probe = field;
83
+ return typeof probe.volatile === 'function' ? probe.volatile() : field;
84
+ }
66
85
  export const Config = z.object({
67
- enabled: z.boolean().default(true),
86
+ // Live so the Web Plugins page can flip the gate injection inside a running
87
+ // session: dsh's settings service projects ONLY fields under a `.volatile()`
88
+ // node and rejects writes to every other path. The price is that the injection
89
+ // reads the reference per step instead of deciding once at mount, which is also
90
+ // what lets a toggle take effect without remounting the row.
91
+ enabled: live(z.boolean().default(true)),
68
92
  gateContent: z.string().default(DEFAULT_GATE_CONTENT),
69
93
  interaction: z.union(['ask', 'auto', 'follow-approval']).default('follow-approval'),
70
94
  });
95
+ /**
96
+ * Read the injection switch as a boolean, whichever shape this host produced.
97
+ * @param config - the resolved plugin configuration.
98
+ * @returns whether the gate may be injected.
99
+ */
100
+ function injectionEnabled(config) {
101
+ const value = config.enabled;
102
+ return typeof value === 'boolean' ? value : value.get();
103
+ }
71
104
  function gateMessage(text) {
72
105
  return createUserMessage({
73
106
  content: [{ type: 'text', text }],
@@ -114,6 +147,7 @@ function modeContextText(config, session) {
114
147
  const interaction = resolveInteraction(config, session);
115
148
  const lines = [
116
149
  'logicprobe: use `logicprobe_verify` for state machines and `logicprobe_datamodel_verify` for data models; both cover before/after regression and common domain constraints.',
150
+ 'logicprobe: use `logicprobe_uml` to model a code flow as UML (render), to read a UML diagram back into a model (parse), or to audit the modelling (review: structural defects, documentation gaps, diagram-vs-model round-trip fidelity).',
117
151
  interaction === 'auto'
118
152
  ? 'logicprobe interaction=auto: do NOT call ask_user_question for model confirmation; run round-trip validation of the extracted transition table and mark the result UNCONFIRMED.'
119
153
  : 'logicprobe interaction=ask: show the extracted transition table and get user confirmation before running verification.',
@@ -128,7 +162,7 @@ function modeContextText(config, session) {
128
162
  * lets the model read this plugin's runtime status without guessing. Mirrors
129
163
  * the registration pattern of the official dsh-tool-cordis host providers.
130
164
  */
131
- function inspectProvider(config, isToolRegistered, isDataToolRegistered, isConcurrencyToolRegistered, isComposeToolRegistered, isExportToolRegistered) {
165
+ function inspectProvider(config, isToolRegistered, isDataToolRegistered, isConcurrencyToolRegistered, isComposeToolRegistered, isExportToolRegistered, isUmlToolRegistered) {
132
166
  return {
133
167
  manifest: {
134
168
  id: 'logicprobe',
@@ -154,10 +188,11 @@ function inspectProvider(config, isToolRegistered, isDataToolRegistered, isConcu
154
188
  concurrencyToolRegistered: { type: 'boolean', description: 'Whether the logicprobe_concurrency_scan tool is registered on ctx.tools.' },
155
189
  composeToolRegistered: { type: 'boolean', description: 'Whether the logicprobe_compose_verify tool is registered on ctx.tools.' },
156
190
  exportToolRegistered: { type: 'boolean', description: 'Whether the logicprobe_export tool is registered on ctx.tools.' },
191
+ umlToolRegistered: { type: 'boolean', description: 'Whether the logicprobe_uml tool (UML modelling + modelling review) is registered on ctx.tools.' },
157
192
  engineSchemaVersion: { type: 'integer', description: 'Model schema version the bundled state-machine verification engine accepts.' },
158
193
  dataEngineSchemaVersion: { type: 'integer', description: 'Model schema version the bundled data-model verification engine accepts.' },
159
194
  },
160
- required: ['enabled', 'gateContentLength', 'interaction', 'toolRegistered', 'dataToolRegistered', 'concurrencyToolRegistered', 'composeToolRegistered', 'exportToolRegistered', 'engineSchemaVersion', 'dataEngineSchemaVersion'],
195
+ required: ['enabled', 'gateContentLength', 'interaction', 'toolRegistered', 'dataToolRegistered', 'concurrencyToolRegistered', 'composeToolRegistered', 'exportToolRegistered', 'umlToolRegistered', 'engineSchemaVersion', 'dataEngineSchemaVersion'],
161
196
  additionalProperties: false,
162
197
  },
163
198
  },
@@ -166,7 +201,7 @@ function inspectProvider(config, isToolRegistered, isDataToolRegistered, isConcu
166
201
  query: async (method) => {
167
202
  if (method === 'status') {
168
203
  return {
169
- enabled: config.enabled,
204
+ enabled: injectionEnabled(config),
170
205
  gateContentLength: config.gateContent.length,
171
206
  interaction: config.interaction,
172
207
  toolRegistered: isToolRegistered(),
@@ -174,6 +209,7 @@ function inspectProvider(config, isToolRegistered, isDataToolRegistered, isConcu
174
209
  concurrencyToolRegistered: isConcurrencyToolRegistered(),
175
210
  composeToolRegistered: isComposeToolRegistered(),
176
211
  exportToolRegistered: isExportToolRegistered(),
212
+ umlToolRegistered: isUmlToolRegistered(),
177
213
  engineSchemaVersion: ENGINE_SCHEMA_VERSION,
178
214
  dataEngineSchemaVersion: DATA_ENGINE_SCHEMA_VERSION,
179
215
  };
@@ -192,6 +228,7 @@ export function apply(ctx, config) {
192
228
  let concurrencyToolRegistered = false;
193
229
  let composeToolRegistered = false;
194
230
  let exportToolRegistered = false;
231
+ let umlToolRegistered = false;
195
232
  let modeContextRegistered = false;
196
233
  const registerProvider = () => {
197
234
  if (providerRegistered)
@@ -200,7 +237,7 @@ export function apply(ctx, config) {
200
237
  if (inspect === undefined)
201
238
  return;
202
239
  try {
203
- ctx.effect(() => inspect.register(inspectProvider(config, () => toolRegistered, () => dataToolRegistered, () => concurrencyToolRegistered, () => composeToolRegistered, () => exportToolRegistered)), 'logicprobe: inspect provider');
240
+ ctx.effect(() => inspect.register(inspectProvider(config, () => toolRegistered, () => dataToolRegistered, () => concurrencyToolRegistered, () => composeToolRegistered, () => exportToolRegistered, () => umlToolRegistered)), 'logicprobe: inspect provider');
204
241
  providerRegistered = true;
205
242
  }
206
243
  catch (err) {
@@ -219,11 +256,13 @@ export function apply(ctx, config) {
219
256
  ctx.effect(() => tools.register(logicProbeConcurrencyScanTool), 'logicprobe: concurrency scan tool');
220
257
  ctx.effect(() => tools.register(logicProbeComposeTool), 'logicprobe: compose tool');
221
258
  ctx.effect(() => tools.register(logicProbeExportTool), 'logicprobe: export tool');
259
+ ctx.effect(() => tools.register(logicProbeUmlTool), 'logicprobe: uml tool');
222
260
  toolRegistered = true;
223
261
  dataToolRegistered = true;
224
262
  concurrencyToolRegistered = true;
225
263
  composeToolRegistered = true;
226
264
  exportToolRegistered = true;
265
+ umlToolRegistered = true;
227
266
  }
228
267
  catch (err) {
229
268
  console.warn('[logicprobe] logicprobe_verify/logicprobe_datamodel_verify tool registration failed', err);
@@ -272,8 +311,12 @@ export function apply(ctx, config) {
272
311
  customSkillDirs: [SKILLS_DIR],
273
312
  });
274
313
  });
275
- if (!config.enabled)
276
- return;
314
+ // Injection listens unconditionally, including while the switch is off: the
315
+ // switch is volatile, so `apply` runs once and the value behind it can turn on
316
+ // later from the Web Plugins page. Returning early on a false value here would
317
+ // freeze that decision for the lifetime of the mount, and turning the switch
318
+ // back on could never take effect without a profile restart.
319
+ //
277
320
  // Inject the gate once per session on the FIRST model step that runs,
278
321
  // instead of at session-start: session-start injection lands in the agent's
279
322
  // inbox, which a blank-session preset switch (agentPreset.select ->
@@ -290,6 +333,8 @@ export function apply(ctx, config) {
290
333
  if (decision.kind === 'reject')
291
334
  return decision;
292
335
  registerIntegrations();
336
+ if (!injectionEnabled(config))
337
+ return decision;
293
338
  if (gateInHistory(agent.session))
294
339
  return decision;
295
340
  return {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Structural mirror of the JSON value union the dsh tool registry accepts.
3
+ *
4
+ * `@deepseek-ai/dsh-tools` exported this type through the 0.1.x line and stopped
5
+ * exporting it in 0.2.x, where the same type now lives in
6
+ * `@deepseek-ai/dsh-util-values`. This package declares peer support for both
7
+ * lines, so it cannot import the type from either one without breaking the other
8
+ * build. Declaring the one-line union here keeps `npm run typecheck` green against
9
+ * both, and it costs nothing at runtime: every use is a type-only cast on a value
10
+ * this package already produced.
11
+ *
12
+ * Keep it structurally identical to the official type. A cast through `unknown` to
13
+ * this alias is only a way to satisfy `defineTool`'s return-type constraint; if the
14
+ * official union ever widens, this alias must widen with it.
15
+ *
16
+ * @module logicprobe-json-value
17
+ */
18
+ export {};
@@ -24,7 +24,7 @@
24
24
  *
25
25
  * @module logicprobe-dsh
26
26
  */
27
- import type { Context } from '@deepseek-ai/cordis';
27
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
28
28
  import z from '@deepseek-ai/schemastery';
29
29
  import type { ContextFormed } from '@deepseek-ai/dsh-llm';
30
30
  declare module '@deepseek-ai/dsh-llm' {
@@ -38,17 +38,23 @@ export declare const name = "logicprobe";
38
38
  export declare const inject: string[];
39
39
  export type InteractionMode = 'ask' | 'auto' | 'follow-approval';
40
40
  export interface Config {
41
- enabled: boolean;
41
+ /**
42
+ * The injection switch the Web client's Plugins page edits live: a `Volatile`
43
+ * reference on a host whose schemastery supports one, an ordinary boolean on a
44
+ * host that predates `.volatile()`. Read it through {@link injectionEnabled},
45
+ * which accepts both shapes.
46
+ */
47
+ enabled: Volatile<boolean> | boolean;
42
48
  gateContent: string;
43
49
  interaction: InteractionMode;
44
50
  }
45
- export declare const Config: z<Schemastery.ObjectS<{
46
- enabled: z<boolean, boolean>;
47
- gateContent: z<string, string>;
48
- interaction: z<"ask" | "auto" | "follow-approval", "ask" | "auto" | "follow-approval">;
49
- }>, Schemastery.ObjectT<{
50
- enabled: z<boolean, boolean>;
51
- gateContent: z<string, string>;
52
- interaction: z<"ask" | "auto" | "follow-approval", "ask" | "auto" | "follow-approval">;
53
- }>>;
51
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
52
+ enabled: z<boolean, boolean, "defined">;
53
+ gateContent: z<string, string, "defined">;
54
+ interaction: z<"ask" | "auto" | "follow-approval", "ask" | "auto" | "follow-approval", "defined">;
55
+ }>>, Schemastery.ObjectT<NoInfer<{
56
+ enabled: z<boolean, boolean, "defined">;
57
+ gateContent: z<string, string, "defined">;
58
+ interaction: z<"ask" | "auto" | "follow-approval", "ask" | "auto" | "follow-approval", "defined">;
59
+ }>>, "plain">;
54
60
  export declare function apply(ctx: Context, config: Config): void;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Structural mirror of the JSON value union the dsh tool registry accepts.
3
+ *
4
+ * `@deepseek-ai/dsh-tools` exported this type through the 0.1.x line and stopped
5
+ * exporting it in 0.2.x, where the same type now lives in
6
+ * `@deepseek-ai/dsh-util-values`. This package declares peer support for both
7
+ * lines, so it cannot import the type from either one without breaking the other
8
+ * build. Declaring the one-line union here keeps `npm run typecheck` green against
9
+ * both, and it costs nothing at runtime: every use is a type-only cast on a value
10
+ * this package already produced.
11
+ *
12
+ * Keep it structurally identical to the official type. A cast through `unknown` to
13
+ * this alias is only a way to satisfy `defineTool`'s return-type constraint; if the
14
+ * official union ever widens, this alias must widen with it.
15
+ *
16
+ * @module logicprobe-json-value
17
+ */
18
+ /** JSON-serializable value: null, boolean, number, string, or an array/object of those. */
19
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
20
+ [key: string]: JsonValue;
21
+ };
@@ -0,0 +1,14 @@
1
+ export declare const LOGICPROBE_UML_TOOL_NAME = "logicprobe_uml";
2
+ /**
3
+ * DSH tool wrapping the UML front end: model a code flow as a UML diagram
4
+ * (`action: "render"`), read a UML diagram back into a LogicModelV1
5
+ * (`action: "parse"`), or review the modelling itself (`action: "review"`).
6
+ *
7
+ * review is the half that makes the feature a check rather than a drawing
8
+ * utility: it reports structural defects the diagram would present as valid
9
+ * flow (unreachable states, dead ends, ambiguous and non-exhaustive branches,
10
+ * self-loops with no exit), documentation gaps (a symbol no reader can map back
11
+ * to code), and — the fidelity check — whether the rendered diagram reads back
12
+ * as the model it was drawn from.
13
+ */
14
+ export declare const logicProbeUmlTool: import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,167 @@
1
+ /**
2
+ * UML front end for LogicModelV1 — model a code flow as a UML diagram, then
3
+ * review the modelling itself.
4
+ *
5
+ * Two halves, one data flow:
6
+ *
7
+ * 1. `renderUml` turns a validated LogicModelV1 into Mermaid or PlantUML text
8
+ * (state machine, activity/flow, or sequence trace). The diagram is a
9
+ * *view*: it never invents structure the model does not have, and anything
10
+ * the notation cannot express is reported as a warning instead of being
11
+ * dropped silently.
12
+ * 2. `parseUml` reads that text back into a LogicModelV1, and `reviewUml`
13
+ * compares the two. That comparison is the point of the feature: a UML
14
+ * diagram of a code flow is itself a model, and a model can be wrong —
15
+ * ambiguous branches, dead ends, flows nobody can enter, symbols the
16
+ * source never names. A diagram that does not round-trip to the model it
17
+ * was drawn from is mis-modelled, and the review says so.
18
+ *
19
+ * Why the round trip is the fidelity check: rendering and parsing are inverse
20
+ * only if every construct survives the notation. The generated text carries
21
+ * `logicprobe:` directives (ignored by Mermaid/PlantUML renderers) that pin the
22
+ * initial state, the terminal states and any state id the notation cannot spell
23
+ * verbatim, so an exact comparison is possible rather than a fuzzy one.
24
+ *
25
+ * The review deliberately does NOT replace `logicprobe_verify`: it checks the
26
+ * modelling (structure the diagram claims, documentation coverage, notation
27
+ * fidelity), while S1-S8/A1-A14 check the machine's behaviour (guard
28
+ * exhaustiveness under real valuations, invariant paths, deadlock/liveness in
29
+ * the runtime state space). Findings name the engine check to run next.
30
+ *
31
+ * @module logicprobe-uml
32
+ */
33
+ import type { GuardNode, LogicModelV1, UpdateSpec } from './engine.js';
34
+ export type UmlNotation = 'mermaid' | 'plantuml';
35
+ export type UmlDiagram = 'state' | 'activity' | 'sequence';
36
+ export declare const UML_NOTATIONS: readonly UmlNotation[];
37
+ export declare const UML_DIAGRAMS: readonly UmlDiagram[];
38
+ export interface UmlRenderResult {
39
+ notation: UmlNotation;
40
+ diagram: UmlDiagram;
41
+ /** The diagram source; hand this to the user or a renderer as-is. */
42
+ primary: string;
43
+ warnings: string[];
44
+ }
45
+ export interface UmlParseResult {
46
+ notation: UmlNotation;
47
+ diagram: UmlDiagram;
48
+ model: LogicModelV1;
49
+ /**
50
+ * Display labels found in the diagram, keyed by state id. They are how a
51
+ * reader learns what a symbol means; a missing entry is an undocumented
52
+ * symbol, which the review reports.
53
+ */
54
+ labels: Record<string, string>;
55
+ warnings: string[];
56
+ }
57
+ export interface UmlFinding {
58
+ code: string;
59
+ severity: 'error' | 'warning' | 'info';
60
+ message: string;
61
+ states?: string[];
62
+ events?: string[];
63
+ transitions?: Array<{
64
+ from: string;
65
+ event: string;
66
+ to: string;
67
+ }>;
68
+ detail?: string;
69
+ }
70
+ export interface UmlRoundTripReport {
71
+ notation: UmlNotation;
72
+ diagram: UmlDiagram;
73
+ ok: boolean;
74
+ modelHash: string;
75
+ parsedHash: string;
76
+ diffs: string[];
77
+ warnings: string[];
78
+ }
79
+ export interface UmlReviewReport {
80
+ ok: boolean;
81
+ source: 'model' | 'diagram' | 'model+diagram';
82
+ summary: {
83
+ errors: number;
84
+ warnings: number;
85
+ info: number;
86
+ states: number;
87
+ events: number;
88
+ transitions: number;
89
+ terminalStates: number;
90
+ reachableStates: number;
91
+ documentedStates: number;
92
+ };
93
+ findings: UmlFinding[];
94
+ roundTrip: UmlRoundTripReport | null;
95
+ /** Diagram display labels, when a diagram took part in the review. */
96
+ labels?: Record<string, string>;
97
+ /** Model parsed from the diagram, when a diagram was given (feed it to `logicprobe_verify`). */
98
+ model?: LogicModelV1;
99
+ /** Diagram rendered from the model, when only a model was given. */
100
+ primary?: string;
101
+ warnings: string[];
102
+ nextSteps: string[];
103
+ }
104
+ export declare class UmlError extends Error {
105
+ }
106
+ /** Canonical guard text. Rendering wraps every composite node in parentheses, and the parser flattens same-operator chains, so render∘parse is the identity. */
107
+ export declare function guardText(node: GuardNode): string;
108
+ /**
109
+ * Render a LogicModelV1 as UML.
110
+ *
111
+ * PlantUML has no faithful activity view here: its activity syntax is a
112
+ * structured flowchart language, so a graph with merges or cycles needs a
113
+ * while/if reconstruction this module does not perform. Refusing is the honest
114
+ * outcome — quietly emitting a state diagram under an "activity" request would
115
+ * mislabel the model. Mermaid covers all three views.
116
+ *
117
+ * @param input - candidate LogicModelV1.
118
+ * @param notation - `mermaid` (default) or `plantuml`.
119
+ * @param diagram - `state` (default), `activity`, or `sequence`.
120
+ * @param maxSteps - cap on the sequence trace length.
121
+ */
122
+ export declare function renderUml(input: unknown, notation?: UmlNotation, diagram?: UmlDiagram, maxSteps?: number): UmlRenderResult;
123
+ /** Parse a guard expression such as `(retry < 3 && armed == true)`. */
124
+ export declare function parseGuardText(text: string): GuardNode;
125
+ /** Parse a UML action clause such as `retry := retry + 1, armed := true`. */
126
+ export declare function parseUpdatesText(text: string, warnings: string[]): UpdateSpec[];
127
+ /**
128
+ * Parse a Mermaid or PlantUML diagram back into a LogicModelV1.
129
+ *
130
+ * State and activity diagrams carry the whole machine, so they parse into a
131
+ * complete model. Sequence diagrams do not: a trace shows the paths that were
132
+ * walked, not the branches that were not, so parsing one would silently prune
133
+ * the machine. That case is refused rather than approximated.
134
+ *
135
+ * @param text - diagram source.
136
+ * @param notation - `auto` (default) detects Mermaid vs PlantUML from the text.
137
+ */
138
+ export declare function parseUml(text: string, notation?: UmlNotation | 'auto'): UmlParseResult;
139
+ /** Compare two machines by structure — the fidelity measure behind the round-trip check. */
140
+ export declare function diffModels(left: LogicModelV1, right: LogicModelV1): string[];
141
+ export interface UmlReviewOptions {
142
+ /** LogicModelV1 to review. Provide it, `diagram`, or both. */
143
+ model?: unknown;
144
+ /** Diagram text: reviewed on its own, or compared against `model` when both are given. */
145
+ diagram?: string;
146
+ notation?: UmlNotation | 'auto';
147
+ diagramKind?: UmlDiagram;
148
+ /** Render-and-reparse fidelity check (default true; only meaningful with a model). */
149
+ roundTrip?: boolean;
150
+ /** Cap on the sequence trace used for the fidelity check. */
151
+ maxSteps?: number;
152
+ }
153
+ /**
154
+ * Review a UML model of a code flow.
155
+ *
156
+ * Three inputs are possible and each answers a different question:
157
+ *
158
+ * - `model` only — "is this machine well-modelled?" The review checks the
159
+ * structure the diagram would draw, then renders and re-parses it to prove the
160
+ * diagram carries the machine faithfully (round trip).
161
+ * - `diagram` only — "what does this diagram actually say?" The diagram is parsed
162
+ * into a model, and that model is reviewed; nothing can be said about fidelity
163
+ * to a machine the caller did not provide.
164
+ * - both — "does this diagram match this model?" Any structural difference is a
165
+ * modelling defect and is reported both as round-trip diffs and as a finding.
166
+ */
167
+ export declare function reviewUml(options: UmlReviewOptions): UmlReviewReport;