dsh-logicprobe 1.1.0 → 1.3.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 (39) hide show
  1. package/README.en-US.md +17 -6
  2. package/README.md +17 -6
  3. package/lib/compose-tool.js +8 -2
  4. package/lib/concurrency-tool.js +8 -2
  5. package/lib/data-tool.js +8 -2
  6. package/lib/engine.js +3151 -3151
  7. package/lib/export-tool.js +8 -2
  8. package/lib/index.js +28 -40
  9. package/lib/report-diff-tool.js +8 -2
  10. package/lib/report-view.js +295 -0
  11. package/lib/structure-tool.js +8 -2
  12. package/lib/tool.js +8 -2
  13. package/lib/types/engine.d.ts +419 -419
  14. package/lib/types/index.d.ts +5 -4
  15. package/lib/types/report-view.d.ts +43 -0
  16. package/lib/types/uml.d.ts +293 -256
  17. package/lib/uml-tool.js +21 -14
  18. package/lib/uml.js +76 -12
  19. package/package.json +2 -2
  20. package/skills/logicprobe/SKILL.md +42 -6
  21. package/skills/logicprobe/references/dsh-model-schema.md +1 -1
  22. package/skills/logicprobe/references/gap-routing-guide.md +10 -9
  23. package/skills/logicprobe/references/logic-verification-guide.md +1 -1
  24. package/skills/logicprobe-concurrency/SKILL.md +2 -0
  25. package/skills/logicprobe-datamodel/SKILL.md +5 -0
  26. package/skills/logicprobe-structure/SKILL.md +1 -0
  27. package/skills/logicprobe-uml/SKILL.md +3 -0
  28. package/skills/logicprobe-uml/references/uml-modeling-guide.md +70 -2
  29. package/src/compose-tool.ts +8 -2
  30. package/src/concurrency-tool.ts +8 -2
  31. package/src/data-tool.ts +9 -3
  32. package/src/export-tool.ts +8 -2
  33. package/src/index.ts +28 -40
  34. package/src/report-diff-tool.ts +8 -2
  35. package/src/report-view.ts +343 -0
  36. package/src/structure-tool.ts +8 -2
  37. package/src/tool.ts +8 -2
  38. package/src/uml-tool.ts +21 -14
  39. package/src/uml.ts +94 -11
package/lib/uml-tool.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { defineTool } from '@deepseek-ai/dsh-tools';
2
2
  import { createHash } from 'node:crypto';
3
3
  import { REPORT_SCHEMAS, DEFAULT_HASH_SPEC, modelHash, refusalVerdict, verdictOf, verdictOfFindings } from './engine.js';
4
- import { renderUml, parseUml, parseFindings, reviewUml, explainLabels, UmlError } from './uml.js';
4
+ import { renderUml, parseUml, parseFindings, reviewUml, explainLabels, checkUmlIntent, UmlError } from './uml.js';
5
+ import { renderReportText } from './report-view.js';
5
6
  export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml';
6
7
  /**
7
8
  * DSH tool wrapping the UML front end: model a code flow as a UML diagram
@@ -17,7 +18,7 @@ export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml';
17
18
  */
18
19
  export const logicProbeUmlTool = defineTool({
19
20
  name: LOGICPROBE_UML_TOOL_NAME,
20
- description: 'Model a code flow as UML, and audit the modelling. action="render" turns a LogicModelV1 into diagram source: mermaid (state, activity, sequence) or plantuml (state, sequence). action="parse" reads Mermaid or PlantUML state or activity text back into a LogicModelV1, so a hand-drawn diagram can be verified. A sequence diagram is refused: a trace cannot reconstruct a machine. So is any other diagram family (class, ER, gantt, mindmap). That refusal carries errorCode "UML_NOT_A_STATE_DIAGRAM". A PlantUML component, package or deployment diagram declares no kind, so the parser detects those constructs itself. It returns a by-product model and reports the error UML_NOT_A_STATE_DIAGRAM, with `discardedConstructs` and `discardedEdges`. Never treat that model as a model of the file. action="review" audits the modelling: structural defects (UML002-UML009), documentation gaps (UML010-UML015), and a fidelity check that re-parses the rendered diagram (UML017). Give it a model, a diagram, or both. action="explain-labels" prints the label and comment conventions the parser accepts, without needing a diagram. Rendering never invents structure. A construct the notation cannot express becomes a warning. For dependency questions on a component or class diagram, use `logicprobe_structure_verify` instead. Read `verdict`, not `ok`: error findings mean a failed review.',
21
+ description: 'Model a code flow as UML, and audit the modelling. action="render" turns a LogicModelV1 into diagram source: mermaid (state, activity, sequence) or plantuml (state, sequence). action="parse" reads Mermaid or PlantUML state or activity text back into a LogicModelV1, so a hand-drawn diagram can be verified. A sequence diagram is refused: a trace cannot reconstruct a machine. So is any other diagram family (class, ER, gantt, mindmap). That refusal carries errorCode "UML_NOT_A_STATE_DIAGRAM". A PlantUML component, package or deployment diagram declares no kind, so the parser detects those constructs itself. It returns a by-product model and reports the error UML_NOT_A_STATE_DIAGRAM, with `discardedConstructs` and `discardedEdges`. Never treat that model as a model of the file. action="review" audits the modelling: structural defects (UML002-UML009), documentation gaps (UML010-UML015), and a fidelity check that re-parses the rendered diagram (UML017). Give it a model, a diagram, or both. action="explain-labels" prints the label and comment conventions the parser accepts, without needing a diagram. Rendering never invents structure. A construct the notation cannot express becomes a warning. For dependency questions on a component or class diagram, use `logicprobe_structure_verify` instead. Declare `intent` (present, verify, fidelity) to say what the artifact is for: a view that cannot serve it is refused before any work, with errorCode UML_INTENT_UNSERVABLE, UML_VIEW_UNREPRESENTABLE or UML_INTENT_MISMATCH. Omit it and nothing changes. Read `verdict`, not `ok`: error findings mean a failed review.',
21
22
  parameters: {
22
23
  action: {
23
24
  type: 'string',
@@ -43,6 +44,11 @@ export const logicProbeUmlTool = defineTool({
43
44
  enum: ['state', 'activity', 'sequence'],
44
45
  description: 'Diagram kind to render. Default state. sequence is one BFS trace, not the whole machine. PlantUML activity is refused (its structured-flowchart syntax cannot faithfully carry a graph with merges or cycles) — use mermaid for that view.',
45
46
  },
47
+ intent: {
48
+ type: 'string',
49
+ enum: ['present', 'verify', 'fidelity'],
50
+ description: 'What the artifact is for, declared up front: present = a human will read the diagram (any view), verify = it must be parseable into a LogicModelV1 for the engine (state or activity), fidelity = the point is the render-and-reparse proof (review only, state or activity). A view that cannot serve the intent is refused before any work, with errorCode UML_INTENT_UNSERVABLE, UML_VIEW_UNREPRESENTABLE or UML_INTENT_MISMATCH. Omit it and nothing changes: a sequence view is still rendered, and review still reports UML019.',
51
+ },
46
52
  roundTrip: {
47
53
  type: 'boolean',
48
54
  description: 'review only: render and re-parse the model to prove the diagram carries it. Default true.',
@@ -51,23 +57,21 @@ export const logicProbeUmlTool = defineTool({
51
57
  type: 'integer',
52
58
  description: 'Cap on the sequence trace length when rendering kind=sequence. Default 60.',
53
59
  },
60
+ reportView: {
61
+ type: 'string',
62
+ enum: ['digest', 'full'],
63
+ description: 'digest (default) prints the diagram or report body; full prints the structured result as JSON.',
64
+ },
54
65
  },
55
66
  output: {
56
67
  schema: {
57
68
  type: 'json',
58
69
  description: 'Render result (diagram source), parse result (LogicModelV1 + labels), or the modelling review report with findings, round-trip diff and next steps.',
59
70
  },
60
- render(_args, value) {
61
- const record = value;
62
- // A rendered diagram is source text: print it verbatim so it can be copied
63
- // into a file, then the structured result (warnings, notation, kind).
64
- if (typeof record.diagram === 'string') {
65
- return [
66
- { type: 'text', text: record.diagram },
67
- { type: 'text', text: JSON.stringify(value, null, 2) },
68
- ];
69
- }
70
- return [{ type: 'text', text: JSON.stringify(value, null, 2) }];
71
+ render(args, value) {
72
+ // A rendered diagram is source text: the report body prints it verbatim, once.
73
+ // Echoing the whole result as JSON printed the same diagram a second time, escaped.
74
+ return [{ type: 'text', text: renderReportText(value, { full: args.reportView === 'full' }) }];
71
75
  },
72
76
  },
73
77
  timeoutMs: 10_000,
@@ -77,12 +81,13 @@ export const logicProbeUmlTool = defineTool({
77
81
  if (args.action === 'render') {
78
82
  if (args.model === undefined)
79
83
  return errorResult('action=render needs `model` (a LogicModelV1 object)');
80
- const result = renderUml(args.model, (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation), (args.kind ?? 'state'), args.maxSteps);
84
+ const result = renderUml(args.model, (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation), (args.kind ?? 'state'), args.maxSteps, args.intent);
81
85
  return { ok: true, ran: true, ...verdictOf(0, result.warnings.length), schema: REPORT_SCHEMAS.umlRender, action: 'render', notation: result.notation, kind: result.diagram, diagram: result.primary, hashes: { diagram: createHash('sha256').update(result.primary).digest('hex') }, warnings: result.warnings };
82
86
  }
83
87
  if (args.action === 'parse') {
84
88
  if (args.diagram === undefined)
85
89
  return errorResult('action=parse needs `diagram` (Mermaid or PlantUML text)');
90
+ checkUmlIntent(args.intent, 'parse');
86
91
  const result = parseUml(args.diagram, args.notation ?? 'auto');
87
92
  // A diagram from another family still parses into *something*. `parseFindings`
88
93
  // says so, and the verdict is what stops that something from being trusted:
@@ -108,6 +113,7 @@ export const logicProbeUmlTool = defineTool({
108
113
  // R9: the label contract was documented in the library and in `explainLabels()`, but no
109
114
  // tool exposed it, so it was reachable only by reading the source or the docs.
110
115
  if (args.action === 'explain-labels') {
116
+ checkUmlIntent(args.intent, 'labels');
111
117
  const notation = (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation);
112
118
  return {
113
119
  ok: true,
@@ -125,6 +131,7 @@ export const logicProbeUmlTool = defineTool({
125
131
  diagramKind: (args.kind ?? 'state'),
126
132
  roundTrip: args.roundTrip,
127
133
  maxSteps: args.maxSteps,
134
+ intent: args.intent,
128
135
  });
129
136
  return report;
130
137
  }
package/lib/uml.js CHANGED
@@ -33,6 +33,7 @@
33
33
  import { REPORT_SCHEMAS, validateModel, modelHash, metadataKeysOf, narrativeComplete, narrativeCoverageOf, verdictOfFindings, DEFAULT_HASH_SPEC } from './engine.js';
34
34
  export const UML_NOTATIONS = ['mermaid', 'plantuml'];
35
35
  export const UML_DIAGRAMS = ['state', 'activity', 'sequence'];
36
+ export const UML_INTENTS = ['present', 'verify', 'fidelity'];
36
37
  /** Marker every generated diagram carries; renderers ignore it, the parser uses it. */
37
38
  const DIRECTIVE_NAMESPACE = 'logicprobe:';
38
39
  /** State id / event name characters that would break the generated label syntax. */
@@ -286,28 +287,90 @@ function renderSequence(context, notation, maxSteps) {
286
287
  lines.push('@enduml');
287
288
  return { text: lines.join('\n') + '\n', truncated };
288
289
  }
290
+ /** What each action hands back, so a declared intent can be matched against it. */
291
+ const ACTION_PRODUCES = {
292
+ render: 'a diagram',
293
+ parse: 'a LogicModelV1 read out of a diagram',
294
+ review: 'an audit of the modelling',
295
+ labels: 'the label and comment conventions the parser accepts',
296
+ };
289
297
  /**
290
- * Render a LogicModelV1 as UML.
298
+ * The intents each action can serve.
291
299
  *
292
- * PlantUML has no faithful activity view here: its activity syntax is a
293
- * structured flowchart language, so a graph with merges or cycles needs a
294
- * while/if reconstruction this module does not perform. Refusing is the honest
295
- * outcome — quietly emitting a state diagram under an "activity" request would
296
- * mislabel the model. Mermaid covers all three views.
300
+ * `fidelity` is a review job: render produces a diagram, and a diagram nothing has
301
+ * compared back to its model proves nothing about it.
302
+ */
303
+ const ACTION_INTENTS = {
304
+ render: ['present', 'verify'],
305
+ parse: ['verify'],
306
+ review: ['present', 'verify', 'fidelity'],
307
+ labels: [],
308
+ };
309
+ /**
310
+ * The `(notation, view)` cells this module can draw.
297
311
  *
298
- * @param input - candidate LogicModelV1.
299
- * @param notation - `mermaid` (default) or `plantuml`.
300
- * @param diagram - `state` (default), `activity`, or `sequence`.
301
- * @param maxSteps - cap on the sequence trace length.
312
+ * PlantUML activity is the cell that does not exist: its activity syntax is a
313
+ * structured flowchart language, so a graph with merges or cycles needs a while/if
314
+ * reconstruction this module does not perform. Refusing is the honest outcome —
315
+ * quietly emitting a state diagram under an "activity" request would mislabel the
316
+ * model.
302
317
  */
303
- export function renderUml(input, notation = 'mermaid', diagram = 'state', maxSteps = 60) {
318
+ export function assertUmlView(notation, diagram) {
304
319
  if (!UML_NOTATIONS.includes(notation))
305
320
  throw new UmlError('unknown notation "' + String(notation) + '"; expected ' + UML_NOTATIONS.join(' | '));
306
321
  if (!UML_DIAGRAMS.includes(diagram))
307
322
  throw new UmlError('unknown diagram "' + String(diagram) + '"; expected ' + UML_DIAGRAMS.join(' | '));
308
323
  if (notation === 'plantuml' && diagram === 'activity') {
309
- throw new UmlError('plantuml has no faithful activity view here (its activity syntax is a structured flowchart language; a graph with merges or cycles needs a while/if reconstruction logicprobe does not perform) — use notation "mermaid" for the activity view, or diagram "state"');
324
+ throw new UmlError('plantuml has no faithful activity view here (its activity syntax is a structured flowchart language; a graph with merges or cycles needs a while/if reconstruction logicprobe does not perform) — use notation "mermaid" for the activity view, or diagram "state"', 'UML_VIEW_UNREPRESENTABLE');
310
325
  }
326
+ }
327
+ /**
328
+ * Refuse, before any work, a request whose declared purpose the view cannot serve.
329
+ *
330
+ * Without an intent this changes nothing: the review still reports `UML019` when the
331
+ * fidelity check does not apply to the view it was given. With one, an impossible
332
+ * pairing is an error carrying a code, instead of a report about the wrong artifact —
333
+ * which is the failure this check exists to prevent.
334
+ *
335
+ * @param intent - the declared purpose, or `undefined` to leave the behaviour unchanged.
336
+ * @param action - which action is about to run.
337
+ * @param view - the requested `(notation, diagram)`, for the actions that have one.
338
+ */
339
+ export function checkUmlIntent(intent, action, view) {
340
+ if (intent === undefined)
341
+ return;
342
+ if (!UML_INTENTS.includes(intent))
343
+ throw new UmlError('unknown intent "' + String(intent) + '"; expected ' + UML_INTENTS.join(' | '));
344
+ const served = ACTION_INTENTS[action];
345
+ if (!served.includes(intent)) {
346
+ throw new UmlError('intent "' + intent + '" cannot be served by action=' + action + ', which returns ' + ACTION_PRODUCES[action] + '; it serves ' +
347
+ (served.length === 0 ? 'no intent — leave `intent` off' : served.join(' | ')), 'UML_INTENT_MISMATCH');
348
+ }
349
+ if (view === undefined)
350
+ return;
351
+ assertUmlView(view.notation, view.diagram);
352
+ if (intent === 'present' || view.diagram !== 'sequence')
353
+ return;
354
+ throw new UmlError('intent "' + intent + '" needs a machine, and a sequence view is one BFS trace: ' +
355
+ (intent === 'fidelity'
356
+ ? 'the render/parse fidelity check does not apply to a trace, so nothing would be proven. Review kind "state" or "activity" to have the diagram checked against the model, and render the trace separately with intent "present".'
357
+ : '`parse` refuses a trace because it cannot reconstruct a machine, so the diagram cannot feed verification. Model the machine as a state or activity diagram, and render the trace separately with intent "present".'), 'UML_INTENT_UNSERVABLE');
358
+ }
359
+ /**
360
+ * Render a LogicModelV1 as UML.
361
+ *
362
+ * Mermaid covers all three views; PlantUML covers state and sequence (see
363
+ * `assertUmlView` for why activity is refused).
364
+ *
365
+ * @param input - candidate LogicModelV1.
366
+ * @param notation - `mermaid` (default) or `plantuml`.
367
+ * @param diagram - `state` (default), `activity`, or `sequence`.
368
+ * @param maxSteps - cap on the sequence trace length.
369
+ * @param intent - what the diagram is for; a view that cannot serve it is refused first.
370
+ */
371
+ export function renderUml(input, notation = 'mermaid', diagram = 'state', maxSteps = 60, intent) {
372
+ assertUmlView(notation, diagram);
373
+ checkUmlIntent(intent, 'render', { notation, diagram });
311
374
  const context = prepareRender(input);
312
375
  const warnings = [...context.warnings];
313
376
  let primary;
@@ -1563,6 +1626,7 @@ export function reviewUml(options) {
1563
1626
  throw new UmlError('review needs `model`, `diagram`, or both');
1564
1627
  let notation = options.notation === undefined || options.notation === 'auto' ? 'mermaid' : options.notation;
1565
1628
  const kind = options.diagramKind ?? 'state';
1629
+ checkUmlIntent(options.intent, 'review', { notation, diagram: kind });
1566
1630
  let model;
1567
1631
  let labels;
1568
1632
  let parsed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-logicprobe",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Verifies claims about designs and code. Facts go against the source. Behaviour goes through an executable model: state machines, protocols, composition, data models, migrations. Structure diagrams are read as dependency graphs, at one level or several. Two reports can be compared for regression. Exports UPPAAL, TLA+, PRISM and SPIN input. Ships a native DeepSeek Harness bundle that injects the verification gate into the first model step.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -79,7 +79,7 @@
79
79
  "build": "tsc -p tsconfig.json && node scripts/build-client.mjs",
80
80
  "typecheck": "tsc -p tsconfig.json --noEmit",
81
81
  "bump": "node scripts/bump-version.mjs",
82
- "test:engine": "npm run build && node tests/engine/run.mjs && node tests/data-engine/run.mjs && node tests/concurrency/run.mjs && node tests/uml/run.mjs && node tests/apply-smoke.mjs && node tests/dsh-client-half.test.mjs && node tests/dsh-volatile-fallback.test.mjs && node tests/claude-session-start-hook.test.mjs && node tests/skills/run.mjs && node tests/structure/run.mjs && node tests/baseline/run.mjs && node tests/granularity/run.mjs && node tests/exporters/run.mjs && node tests/external/run.mjs && node tests/python/run.mjs",
82
+ "test:engine": "npm run build && node tests/engine/run.mjs && node tests/data-engine/run.mjs && node tests/concurrency/run.mjs && node tests/uml/run.mjs && node tests/apply-smoke.mjs && node tests/dsh-client-half.test.mjs && node tests/dsh-volatile-fallback.test.mjs && node tests/claude-session-start-hook.test.mjs && node tests/skills/run.mjs && node tests/structure/run.mjs && node tests/baseline/run.mjs && node tests/granularity/run.mjs && node tests/exporters/run.mjs && node tests/report-view/run.mjs && node tests/external/run.mjs && node tests/python/run.mjs",
83
83
  "test:full": "npm run build && node tests/full-suite.mjs",
84
84
  "test:python": "npm run build && node tests/python/run.mjs"
85
85
  },
@@ -60,7 +60,7 @@ This step is NOT skippable — it creates an explicit, auditable record of what
60
60
  |-------|----------|
61
61
  | LIGHTWEIGHT | All 5 checklist items (file paths, API/type names, line numbers, behavioral claims, mechanism feasibility) answered in context with explicit results per item |
62
62
  | STANDARD | Phase 1-2: enumerate every verifiable claim → verify each against codebase with evidence |
63
- | ESCALATED | Full pipeline: Phase 1-5 including Logic Primitive Verification (Phase 2a + 2b, 22 checks) |
63
+ | ESCALATED | Full pipeline: Phase 1-5 including Logic Primitive Verification (Phase 2a structural, Phase 2b adversarial) |
64
64
 
65
65
  ### Plan Verification Block
66
66
 
@@ -151,7 +151,7 @@ Document claims → Extract model → Runtime check:
151
151
 
152
152
  Refactoring variant:
153
153
  Old code + Refactoring plan → Extract BEFORE model + AFTER model
154
- → Run pipeline on AFTER model (22 checks)
154
+ → Run the pipeline on the AFTER model (the same checks)
155
155
  → Compare BEFORE vs AFTER: behavioral preservation, regression, complexity delta
156
156
  → Flag any invariant that held in BEFORE but fails in AFTER
157
157
 
@@ -171,8 +171,10 @@ UML modelling variant (the task is to draw a flow, not to check a claim):
171
171
  | `logicprobe_concurrency_scan` | Mines concurrency claims (thread-safe, lock-free, race condition, mutex, ISR-safe) and routes them to dedicated verification. It does not prove concurrency safety. |
172
172
  | `logicprobe_export` | Emits external-checker input from a verified model: UPPAAL, TLA+, PRISM, SPIN. |
173
173
  | `logicprobe_uml` | render / parse / review / explain-labels for UML modelling — the diagram domain belongs to the `logicprobe-uml` skill. |
174
+ | `logicprobe_structure_verify` | Reviews a structure diagram as a dependency graph. It reports isolated nodes, dangling endpoints, cycles, disallowed edges, layer violations and missing required edges, and it checks refinement across declared levels. The diagram domain belongs to the `logicprobe-structure` skill. |
175
+ | `logicprobe_report_diff` | Compares two reports of the same family. A finding's identity is its check, its code and its machine-readable locator, so reworded prose reads as changed rather than new. This is slice acceptance computed instead of eyeballed. Without the tool, `verify --baseline REPORT` prints the same diff. |
174
176
 
175
- The repository engine mirrors the same checks and carries the `compose` and `export` subcommands for hosts without the native tools.
177
+ The repository engine mirrors the same checks for hosts without the native tools, and its `--help` lists the subcommands. Two capabilities have no mirror. Data-model verification takes the data harness or the manual mode there. The concurrency claim scan is the same story: `logicprobe-concurrency` routes the claims instead. That engine prints the report JSON, not the body — the body is the dsh tools' rendering.
176
178
 
177
179
  ### Reading a Report: `ok` Is Not a Verdict
178
180
 
@@ -191,6 +193,8 @@ Every tool report carries the same outcome contract. **`ok` says a report was pr
191
193
  | `metadataKeys[]` | Paths of the `_`-prefixed annotation keys the schema ignored, e.g. `["_source", "_verified", "states[0]._note"]` (present only when the input carried any). |
192
194
  | `narrativeCoverage` | `{states: "5/5", events: "3/9", scenarios: "0/12"}` when the model carries a narrative — a partial narrative is valid, and this is how much is still undocumented. The gap is also a `NARRATIVE_PARTIAL` (info) finding, so a gate can read it without parsing `nextSteps` text. |
193
195
 
196
+ A tool prints a body, not JSON. Passing checks stay in the `CHECKS` inventory line, and a fact that several checks report is listed once. Only error findings inline their evidence. Pass `reportView: "full"` when you need the structured report itself, for example to hand it to `logicprobe_report_diff`.
197
+
194
198
  Turn the verdict into an answer, never into a summary of `ok`:
195
199
 
196
200
  - `verdict: "pass"` — no error and no warning finding. Safe to report as verified, still with the scope the model covered.
@@ -199,6 +203,25 @@ Turn the verdict into an answer, never into a summary of `ok`:
199
203
 
200
204
  Exit codes: `0` for `pass` / `pass_with_findings`, `2` for `fail`, a refusal, or an unreadable input. In DSH the native tools return the report as data — read `verdict`, and treat `errorCode`/`error` as the refusal path.
201
205
 
206
+ ### Writing the Answer: Name the Finding, Not Its Code
207
+
208
+ `S2_NO_TRANSITIONS`, `UML019`, `DA2` are values of `findings[].code`, not names. A reader
209
+ who has not memorized the schema learns nothing from one, and cannot tell whether their
210
+ machine is sound or stuck.
211
+
212
+ - **Say what it means**, in the user's language and in the terms of their own code:
213
+ 「启动超时后 ERROR 没有任何出口迁移,机器会停在那里」, not 「S2 命中」. The finding's
214
+ `message` is written for the report; the sentence for the user is written from it.
215
+ - **Keep the identifier where the identifier is the subject**: a report field quoted
216
+ verbatim, a baseline diff, a test name, or a question about the check itself.
217
+ - **Name the property, not the checking.** "This path locks without unlocking" beats
218
+ "A4 failed". A check's name earns its place when the reader asked about the checking.
219
+ - **Carry the verdict in words.** "Not verified" or "verified, within the scope of this
220
+ model" — never a bare pass, and never a warning presented without its label.
221
+
222
+ A reader who cannot tell what failed will either trust a report they cannot read, or
223
+ ignore one they should act on.
224
+
202
225
  ### Slice Acceptance: Baseline Diffs
203
226
 
204
227
  The acceptance criterion for a refactoring slice is usually "no **new** findings", not
@@ -231,7 +254,7 @@ When the document under review is a refactoring plan (modifying existing state m
231
254
  1. **Extract the BEFORE model** from the existing codebase (not the plan — verify what the code actually does, not what the plan claims it does)
232
255
  2. **Extract the AFTER model** from the refactoring plan
233
256
  3. **Show both tables AND their model narratives** to the user side by side and confirm the delta is intentional
234
- 4. **Run Phase 2a + 2b on the AFTER model** — same 22 checks as new design
257
+ 4. **Run Phase 2a + 2b on the AFTER model** — the same checks a new design gets
235
258
  5. **Compare BEFORE vs AFTER**:
236
259
 
237
260
  | Check | Method | Severity if Violated |
@@ -248,7 +271,7 @@ When the document under review is a refactoring plan (modifying existing state m
248
271
 
249
272
  Run the checks with the native `logicprobe_verify` tool (model JSON, structured guard DSL, path-aware invariants) — see `references/dsh-model-schema.md`. A host without the tools can run the repository engine against the same JSON. For probe patterns, extraction methodology and manual procedures, load `references/logic-verification-guide.md`.
250
273
 
251
- ### Phase 2a: Structural Primitives (8 Checks)
274
+ ### Phase 2a: Structural Primitives
252
275
 
253
276
  Run these FIRST. They establish basic well-formedness before adversarial probing.
254
277
 
@@ -263,7 +286,7 @@ Run these FIRST. They establish basic well-formedness before adversarial probing
263
286
  | S7 | **Invariant validity** | Does every reachable state satisfy the plan's stated "always/never/guaranteed" assertions? | Error — plan claim is false |
264
287
  | S8 | **Monotonic variables** | If a variable is declared `monotonic: inc/dec`, do all updates respect that direction? | Error — counter can move backwards |
265
288
 
266
- ### Phase 2b: Adversarial Probes (14 Attacks)
289
+ ### Phase 2b: Adversarial Probes
267
290
 
268
291
  Run these SECOND. Each probe actively tries to BREAK the model. If any probe succeeds (finds a violation), the plan has a behavior gap.
269
292
 
@@ -402,6 +425,19 @@ This skill owns behavioural claims. Three neighbouring domains have their own sk
402
425
 
403
426
  ---
404
427
 
428
+ ## Red Flags
429
+
430
+ If you catch yourself thinking any of these, stop:
431
+
432
+ | You think | Reality |
433
+ |-----------|---------|
434
+ | "Too simple to check" | The check is cheap. The silent skip is not. |
435
+ | "I read the code, so I know" | Knowing is not evidence. Cite `file:line`, or run the model, or call the claim unverified. |
436
+ | "The spec or the diagram says so" | Both are claims about the code. Reconcile them with a source-side scan. |
437
+ | "I'll check while implementing" | Check before the decision, not after. |
438
+ | "Reasoning is enough" | Reasoning is not a run. One counter-example kills a universal claim. |
439
+ | "The report passed, so the code is fine" | The report covers the model you gave it. Reconcile it, and say what you did not check. |
440
+
405
441
  ## Rules
406
442
 
407
443
  1. Never trust a document's claim without codebase verification.
@@ -1,6 +1,6 @@
1
1
  # DSH Model Schema v1 — logicprobe_verify
2
2
 
3
- The dsh-native `logicprobe_verify` tool accepts a structured JSON model. The engine runs 22 checks (S1-S8 structural, A1-A14) and returns a JSON report. When `beforeModel` is supplied, it also runs D1-D4 before/after regression checks. Guards and updates are structured data — no code strings, no arbitrary execution.
3
+ The dsh-native `logicprobe_verify` tool accepts a structured JSON model. The engine runs the structural and adversarial checks (S1-S8, A1-A14) and returns a JSON report. When `beforeModel` is supplied, it also runs D1-D4 before/after regression checks. Guards and updates are structured data — no code strings, no arbitrary execution.
4
4
 
5
5
  ## Top-level model
6
6
 
@@ -10,14 +10,15 @@ as outside this engine and points at tooling that can handle it.
10
10
 
11
11
  | Dimension | What logicprobe covers today | Claim examples | Dedicated tooling | Why not here |
12
12
  |---|---|---|---|---|
13
- | Hard real time (timing) | Order, counts, path budgets (A12); time modeled only as counters + events | "500 ms to SAFE", "no deadline miss", "period ≤ 1 ms" | UPPAAL, IMITATOR (timed automata); binary-level timing analysis for WCET | No clocks, deadlines, or period semantics |
13
+ | Hard real time (timing) | Order, counts, path budgets (A12), and deadlines over discrete ticks (A14). Time otherwise appears as counters and events | "500 ms to SAFE", "no deadline miss", "period ≤ 1 ms" | UPPAAL, IMITATOR (timed automata); binary-level timing analysis for WCET | No continuous clocks. A deadline that is not a tick count cannot be expressed |
14
14
  | Preemptive concurrency | Event-order interleavings (A2/A3), resource pairing (A4), idempotent replay (A8) | "thread-safe", "interrupt-safe", "no priority inversion" | TSan/Helgrind (runtime), CBMC (proof), TLA+ (interleaving model), RTOS-aware analysis | Single-threaded event model; no preemption, IRQ nesting, or atomics at instruction level |
15
15
  | Hybrid control (continuous plant) | Discrete transitions and modes only | "switching is stable", "no chattering", "settling within T" | SpaceEx, Flow* (hybrid reachability), Simulink/Stateflow verification | No continuous dynamics; stability is not expressible |
16
16
  | Probabilistic / reliability | Qualitative reachability and invariants | "MTBF ≥ X", "failure rate ≤ p", "availability ≥ 99.9%" | PRISM, Storm (stochastic model checking), fault-tree / FMEA | No probability semantics |
17
17
  | Execution cost / performance | A12 budget checks over declared transition `cost` (absent = 1) | "worst-case dispatch ≤ 100 cycles" | aiT, RapiTime (real WCET at binary level) | `cost` is a modeler label, not measured execution time |
18
- | Multi-machine composition | Single machine verified; cross-machine contract must be documented separately | "A sends E, B always handles E" | CSP (FDR), mCRL2, TLA+ (compositional models) | No composition semantics between machines |
19
- | Nested/hierarchical statecharts | Flat models only; flatten before verification | parent/child states, orthogonal regions | SCXML / Stateflow (native hierarchy) | Flat state list only — flatten manually and re-confirm |
20
- | Write-site / single-owner analysis | **Not covered, and not planned.** logicprobe reviews the diagram and the model; it does not read the source to see *who writes* a variable, a register or a field | "only the ISR writes this", "one owner per flag", "no double write to the CAN mailbox" | A source-side AST/rule check (e.g. the project's own include/arch checker), a static analyzer (clang-tidy, PC-lint, Coverity), or a model checker over the real code (CBMC) | It needs the source and a rule table about statements — a different input than a model or a diagram. `logicprobe_concurrency_scan` will flag the *claim* as unverified and route it here, which is the honest part of this plugin's answer |
18
+ | Multi-machine composition | `logicprobe_compose_verify` (dsh) and the engine's `compose` subcommand (non-dsh) check machines together: C1 composition deadlock, C2 a rendezvous that never fires. They stop at one handshake event per rendezvous | "A sends E, B always handles E", "these two never deadlock" | CSP (FDR), mCRL2, TLA+ (compositional models) | No payloads, no rate ratio and no fairness bound; each machine's own step order is free |
19
+ | Protocol semantics (message contents, channels, per-message ordering) | Events and handshakes between machines | "QoS 1 delivers exactly once", "the broker never reorders", "the queue never overflows" | TLA+, mCRL2, SPIN (Promela channels) | The model has states and events, not messages, queues or payloads |
20
+ | Nested/hierarchical statecharts | Flat models only; flatten before verification | parent/child states, orthogonal regions | SCXML / Stateflow (native hierarchy) | A flat state list only. `logicprobe_compose_verify` places machines side by side; it does not nest states. Flatten manually and re-confirm |
21
+ | Write-site / single-owner analysis | **Not covered, and not planned.** logicprobe reviews the diagram and the model; it does not read the source to see *who writes* a variable, a register or a field | "only the ISR writes this", "one owner per flag", "no double write to the CAN mailbox" | A source-side AST/rule check (e.g. the project's own include/arch checker), a static analyzer (clang-tidy, PC-lint, Coverity), or a model checker over the real code (CBMC) | It needs the source and a rule table about statements — a different input than a model or a diagram. `logicprobe_concurrency_scan` will flag the *claim* as unverified and route it here, which is the honest part of this plugin's answer (it is a dsh-only capability; without it, route the claim by hand) |
21
22
 
22
23
  ## Where the routing surfaces
23
24
 
@@ -30,8 +31,8 @@ as outside this engine and points at tooling that can handle it.
30
31
  interrupt-safe, ...) carry `suggestions` naming dedicated tools.
31
32
  - Manual review: when reading a plan, match its claims against the table above and
32
33
  require dedicated evidence before accepting "always"/"never"/"guaranteed" language
33
- - Generators: `exportModel(model, format)` emits native inputs for UPPAAL (.xta + queries),
34
- TLA+ (TLC spec + safety), PRISM (.pm + .pctl), and SPIN (Promela + ltl) from a
35
- LogicModelV1 — v1 translates the core machine (guards/updates/weights; booleans as
36
- integers) and leaves unrepresentable invariants as explicit warnings.
37
- Self-tests: tests/exporters/run.mjs.
34
+ - Generators: `logicprobe_export` (dsh) and the engine's `export` subcommand (non-dsh) emit
35
+ native inputs for UPPAAL (.xta + queries), TLA+ (TLC spec + safety), PRISM (.pm + .pctl),
36
+ and SPIN (Promela + ltl) from a LogicModelV1. Both translate the core machine (guards,
37
+ updates, weights; booleans as integers) and return unrepresentable invariants as explicit
38
+ warnings.
@@ -202,7 +202,7 @@ When a probe finds a counter-example, classify it:
202
202
 
203
203
  ### Model Fidelity Warning
204
204
 
205
- The model is an APPROXIMATION. It models state transitions, not execution semantics. A model that passes all 22 checks (S1-S8 structural, A1-A14) means the plan's LOGIC is consistent — NOT that the implementation will work. Always follow logic verification with code-level review.
205
+ The model is an APPROXIMATION. It models state transitions, not execution semantics. A model that passes every check (S1-S8 structural, A1-A14) means the plan's LOGIC is consistent — NOT that the implementation will work. Always follow logic verification with code-level review.
206
206
 
207
207
  ## Refactoring Verification
208
208
 
@@ -41,3 +41,5 @@ The full routing table, including hard-real-time, hybrid-control and execution-c
41
41
  2. **No absolute language in the answer.** "This is thread-safe" is exactly the claim being mined, not a result this skill can produce.
42
42
  3. **One counterexample is enough to refute.** If a race is suspected, the deliverable is the interleaving that breaks it plus the harness that demonstrates it (TSan/Helgrind output), not an argument.
43
43
  4. **Sequential targets are out of scope.** If nothing runs concurrently, say so and stop.
44
+ 5. **The scanner is dsh-only.** `logicprobe_concurrency_scan` has no mirror subcommand. Without it, mine the text by hand against the keyword table above and route each claim the same way.
45
+ 6. **Name the claim and its routing in words.** 「QoS 1 只投递一次」plus the tool that decides it, never a bare check name. A reader who has not memorized this skill's tables learns nothing from an enumeration. The full rule is under "Writing the Answer" in the `logicprobe` skill.
@@ -92,6 +92,11 @@ The **DataModelV1 shape** is in [`references/data-model-schema.md`](references/d
92
92
  | DD3 | Delta summary | Warning |
93
93
  | DD4 | Breaking change regression | Error/Warning |
94
94
 
95
+ Every `#` in these tables is a report field, not a name. Write what the check found for the
96
+ reader (「回滚与备份不对称」), in their language; keep the id for a report field quoted
97
+ verbatim, a baseline diff or a test name. The full rule is under "Writing the Answer" in the
98
+ `logicprobe` skill.
99
+
95
100
  ## Extraction Rule
96
101
 
97
102
  Before writing any verification code, output a data-model table:
@@ -91,5 +91,6 @@ This tool audits the **diagram**; an include or dependency scan audits the **cod
91
91
  2. **Quote the rule id for every judged edge.** "Not allowed" without the rule is an opinion; `matchedRules` makes it a finding.
92
92
  3. **Run the numbers, never the impression.** An isolated node and a back edge are exactly what a reader misses by eye; that is why the check exists.
93
93
  4. **Report the diagram-code difference, not just the diagram.** A structure review that never touches the source is a review of a drawing.
94
+ 5. **Report the rule id with its meaning, never the id alone.** Rule 2 keeps the id on every judged edge, so the finding is defensible; the sentence the reader gets says what the edge violated (「HAL 反向依赖了上层」), in their language. The full rule is under "Writing the Answer" in the `logicprobe` skill.
94
95
 
95
96
  The full matrix schema, a worked example and the reconciliation worksheet are in [`references/structure-review-guide.md`](references/structure-review-guide.md).
@@ -32,6 +32,8 @@ code flow → model (citation per element) → logicprobe_uml action=render →
32
32
  - **review** — it answers one of three questions. Give it a model: is the machine well-modelled? Give it a diagram: what does the diagram say? Give it both: does the diagram match the model?
33
33
  - **explain-labels** — the parser's own account of labels and comments: the accepted spellings, the form the renderer writes, and which lines it ignores. No diagram needed. It answers "why does my hand-drawn diagram report drift or ignored lines?" without reading the source.
34
34
 
35
+ The view follows the question, not the machine, and no tool picks it for you. Declare the question with `intent` (`present` / `verify` / `fidelity`) and a view that cannot serve it is refused before any work, with a code that names why. The ordered procedure, the intent-by-action table and those codes are under [Choosing the view](references/uml-modeling-guide.md#choosing-the-view).
36
+
35
37
  A host without the tools runs the same four actions through the repository engine. The engine is deliberately single-source — do not copy it into this skill.
36
38
 
37
39
  ## A Structure Diagram Is the Dangerous Case, Because It Parses
@@ -53,5 +55,6 @@ Structural defects: unreachable states, dead ends, ambiguous or non-exhaustive b
53
55
  3. **Check the verdict before the picture.** `ok: true` with `verdict: "fail"` means the review failed; a diagram whose review failed is not something to show as if it were checked.
54
56
  4. **The review never replaces verification.** It covers the modelling. S1-S8 and A1-A14 cover the behaviour, and each finding names the check that settles it — hand those over to the `logicprobe` skill.
55
57
  5. **Keep the narrative with the model.** Write `narrative.states`, `narrative.events` and `narrative.scenarios`. Then the diagram stays readable against the code, and label drift shows up as a finding instead of as a stale picture.
58
+ 6. **Name a finding for the reader, not by its code.** `UML017_ROUND_TRIP_MISMATCH` is a report field; the sentence the reader gets says what disagreed (「图里少了 ERROR→COOLDOWN 这条迁移」), in their language. Keep the code for a report field quoted verbatim, a diff or a test name. The full rule is under "Writing the Answer" in the `logicprobe` skill.
56
59
 
57
60
  The full checklist (codes `UML001` to `UML019` plus `UML_NOT_A_STATE_DIAGRAM`), the directive format generated diagrams carry, a worked example, and the limits of each view are in [`references/uml-modeling-guide.md`](references/uml-modeling-guide.md).
@@ -4,7 +4,17 @@ This guide covers two jobs: drawing a code flow as UML, and reviewing that drawi
4
4
 
5
5
  One rule runs through the whole guide. **A diagram is a model, and a model can be wrong.** A tidy flow chart is not evidence about the code. Worse, a diagram that disagrees with the model it came from will mislead every reader who trusts it.
6
6
 
7
- ## When to use this
7
+ ## Choosing the view
8
+
9
+ The view follows the question, not the machine. Decide in this order.
10
+
11
+ **1. Is the question about where dependencies point?** Architecture, layering, module
12
+ structure, "may this call that" — that question belongs to the `logicprobe-structure`
13
+ skill, which reads the same component, package, class or deployment text as a dependency
14
+ graph. The state front end refuses that text by name
15
+ (`UML_NOT_A_STATE_DIAGRAM`) instead of reviewing it.
16
+
17
+ **2. Otherwise fix the boundary first, then pick the view by the reader's question.**
8
18
 
9
19
  | Situation | View that answers it |
10
20
  |---|---|
@@ -14,7 +24,65 @@ One rule runs through the whole guide. **A diagram is a model, and a model can b
14
24
  | "Somebody gave me a diagram. Is it right?" | Any view. Feed it to the review and compare it against the code. |
15
25
  | "This diagram and this model disagree." | Feed both to the review. The round-trip check names every difference. |
16
26
 
17
- Do not use this guide for a behavioural claim about a specific machine. That is the job of `logicprobe_verify` (S1-S8 and A1-A14). The review here covers the modelling, not the behaviour. Every finding it produces names the engine check that settles the behavioural half.
27
+ **3. Check the choice against the next step.** A view that cannot feed the check you are
28
+ about to run is the wrong view, however readable it looks.
29
+
30
+ | Next step | Views that can feed it |
31
+ |---|---|
32
+ | `logicprobe_verify` (S1-S8 and A1-A14) | State or activity: `parse` reads either into a LogicModelV1 |
33
+ | `logicprobe_uml action=review` with a model, for the fidelity check | State or activity: only those round-trip |
34
+ | `logicprobe_uml action=review` with a model alone | Any view. With `kind: "sequence"` the trace is rendered and the fidelity check is reported inapplicable (`UML019`) |
35
+ | A human reading a protocol exchange | Sequence, which is the honest view there |
36
+
37
+ **4. Say that out loud, and the tool refuses a pairing that cannot serve it.** `intent`
38
+ declares what the artifact is for. The check runs before any work, so an impossible
39
+ pairing is a refusal with a code rather than a report about the wrong artifact.
40
+
41
+ | Intent | What it means | Actions that serve it | View it needs |
42
+ |---|---|---|---|
43
+ | `present` | a human is going to read the diagram | render, review | any view the notation can draw |
44
+ | `verify` | it must be parseable into a LogicModelV1 for the engine | render, parse, review | state or activity: a trace cannot be parsed back |
45
+ | `fidelity` | the point is the render-and-reparse proof | review | state or activity: only those round-trip |
46
+
47
+ | `errorCode` | What happened |
48
+ |---|---|
49
+ | `UML_INTENT_UNSERVABLE` | The view exists but cannot serve the intent — a sequence view for `verify` or `fidelity`. |
50
+ | `UML_VIEW_UNREPRESENTABLE` | The `(notation, view)` cell does not exist — PlantUML activity. |
51
+ | `UML_INTENT_MISMATCH` | The intent names something this action does not do — `fidelity` on render (render proves nothing), `present` or `fidelity` on parse (parse returns a model). |
52
+
53
+ Omitting `intent` changes nothing: a sequence view still renders, and a review of one
54
+ still reports `UML019`. The intent is an opt-in statement of purpose, and nothing infers
55
+ it. The CLI mirror takes the same values: `--intent present|verify|fidelity` on
56
+ `uml-render`, `uml-parse` and `uml-review`.
57
+
58
+ When the question is still unclear, model the state machine. It is the safe default, and
59
+ the other views render from the same model afterwards.
60
+
61
+ ### Nothing picks the view for you
62
+
63
+ `render` takes `kind` from the caller and defaults to `state`; it never guesses. A guess
64
+ between state and activity would be a presentation decision, and the machine cannot make
65
+ it: the same guarded branch is a state machine to one reader and an error path to another.
66
+ `parse` does detect the notation and the diagram family from the text, and it refuses the
67
+ families it cannot model, by name.
68
+
69
+ Mechanically decidable, and nothing else:
70
+
71
+ - the notation and the family the text declares — a Mermaid header (`stateDiagram`,
72
+ `flowchart`, `sequenceDiagram`, or a known other-family header) or a PlantUML body
73
+ keyword;
74
+ - whether the requested notation-and-kind combination exists at all (PlantUML has no
75
+ activity view here);
76
+ - whether a view can carry the fidelity check (a sequence cannot).
77
+
78
+ Everything else is a fact about the question, not about the artifact. A model can tell you
79
+ its shape — its state count, its guarded branches, its cycles — and its shape does not
80
+ choose the view.
81
+
82
+ Do not use this guide for a behavioural claim about a specific machine. That is the job of
83
+ `logicprobe_verify` (S1-S8 and A1-A14). The review here covers the modelling, not the
84
+ behaviour. Every finding it produces names the engine check that settles the behavioural
85
+ half.
18
86
 
19
87
  ## The three views
20
88
 
@@ -1,6 +1,7 @@
1
1
  import { defineTool } from '@deepseek-ai/dsh-tools'
2
2
  import type { JsonValue } from './json-value.js'
3
3
  import { runCompositionVerification } from './engine.js'
4
+ import { renderReportText } from './report-view.js'
4
5
 
5
6
  export const LOGICPROBE_COMPOSE_TOOL_NAME = 'logicprobe_compose_verify'
6
7
 
@@ -26,14 +27,19 @@ export const logicProbeComposeTool = defineTool({
26
27
  type: 'integer',
27
28
  description: 'Maximum composite states to explore. Default 10000.',
28
29
  },
30
+ reportView: {
31
+ type: 'string',
32
+ enum: ['digest', 'full'],
33
+ description: 'digest (default) prints the readable body; full prints the structured report, which logicprobe_report_diff needs.',
34
+ },
29
35
  },
30
36
  output: {
31
37
  schema: {
32
38
  type: 'json',
33
39
  description: 'Composition verification report with C1/C2 findings.',
34
40
  },
35
- render(_args, value) {
36
- return [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }]
41
+ render(args, value) {
42
+ return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
37
43
  },
38
44
  },
39
45
  timeoutMs: 10_000,
@@ -1,6 +1,7 @@
1
1
  import { defineTool } from '@deepseek-ai/dsh-tools'
2
2
  import type { JsonValue } from './json-value.js'
3
3
  import { runConcurrencyScan } from './concurrency.js'
4
+ import { renderReportText } from './report-view.js'
4
5
 
5
6
  export const LOGICPROBE_CONCURRENCY_SCAN_TOOL_NAME = 'logicprobe_concurrency_scan'
6
7
 
@@ -20,14 +21,19 @@ export const logicProbeConcurrencyScanTool = defineTool({
20
21
  required: true,
21
22
  description: 'Document or plan text to scan for concurrency-related claims.',
22
23
  },
24
+ reportView: {
25
+ type: 'string',
26
+ enum: ['digest', 'full'],
27
+ description: 'digest (default) prints the readable body; full prints the structured report, which logicprobe_report_diff needs.',
28
+ },
23
29
  },
24
30
  output: {
25
31
  schema: {
26
32
  type: 'json',
27
33
  description: 'Concurrency scan report with findings and summary.',
28
34
  },
29
- render(_args, value) {
30
- return [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }]
35
+ render(args, value) {
36
+ return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
31
37
  },
32
38
  },
33
39
  timeoutMs: 10_000,