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.
@@ -0,0 +1,106 @@
1
+ import { defineTool } from '@deepseek-ai/dsh-tools'
2
+ import type { JsonValue } from './json-value.js'
3
+ import { renderUml, parseUml, reviewUml, UmlError, type UmlDiagram, type UmlNotation } from './uml.js'
4
+
5
+ export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml'
6
+
7
+ /**
8
+ * DSH tool wrapping the UML front end: model a code flow as a UML diagram
9
+ * (`action: "render"`), read a UML diagram back into a LogicModelV1
10
+ * (`action: "parse"`), or review the modelling itself (`action: "review"`).
11
+ *
12
+ * review is the half that makes the feature a check rather than a drawing
13
+ * utility: it reports structural defects the diagram would present as valid
14
+ * flow (unreachable states, dead ends, ambiguous and non-exhaustive branches,
15
+ * self-loops with no exit), documentation gaps (a symbol no reader can map back
16
+ * to code), and — the fidelity check — whether the rendered diagram reads back
17
+ * as the model it was drawn from.
18
+ */
19
+ export const logicProbeUmlTool = defineTool({
20
+ name: LOGICPROBE_UML_TOOL_NAME,
21
+ description:
22
+ 'Model a code flow as UML, and review the modelling. action="render" turns a LogicModelV1 (schemaVersion=1) into diagram source: notation mermaid (state | activity flowchart | sequence) or plantuml (state | sequence). action="parse" reads Mermaid/PlantUML state or activity text back into a LogicModelV1 (plus the display labels it found), so a hand-drawn diagram can be verified with logicprobe_verify; a sequence diagram is refused because a trace cannot reconstruct a machine. action="review" audits the modelling: structural defects (UML002 unreachable state, UML003 dead end, UML004 ambiguous branch, UML005 overlapping guard, UML006 inexhaustive branch, UML007 unused event, UML008 self-loop with no exit, UML009 duplicate transition), documentation gaps (UML010 unused variable, UML011 unbounded variable, UML012 no terminal, UML013 no narrative, UML014 undocumented state, UML015 label drift), and the fidelity check — the diagram is rendered and re-parsed and any structural difference is reported as UML017 round-trip mismatch. Give review a model (checks it, renders and re-parses it), a diagram (parses and reviews that), or both (checks the diagram against the model). Rendering never invents structure and never silently drops a construct the notation cannot express: those become warnings. Review does NOT replace logicprobe_verify — it covers the modelling, not behaviour; findings name the engine check to run next.',
23
+ parameters: {
24
+ action: {
25
+ type: 'string',
26
+ required: true,
27
+ enum: ['render', 'parse', 'review'],
28
+ description: 'render = model → UML source; parse = UML source → model; review = audit the modelling (and its fidelity to the model).',
29
+ },
30
+ model: {
31
+ type: 'json',
32
+ description: 'LogicModelV1 machine. Required for render; accepted by review (alone, or next to diagram to check the two against each other).',
33
+ },
34
+ diagram: {
35
+ type: 'string',
36
+ description: 'UML source text. Required for parse; accepted by review.',
37
+ },
38
+ notation: {
39
+ type: 'string',
40
+ enum: ['auto', 'mermaid', 'plantuml'],
41
+ description: 'Diagram language. Default auto: detected from the text for parse/review, mermaid for render.',
42
+ },
43
+ kind: {
44
+ type: 'string',
45
+ enum: ['state', 'activity', 'sequence'],
46
+ 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.',
47
+ },
48
+ roundTrip: {
49
+ type: 'boolean',
50
+ description: 'review only: render and re-parse the model to prove the diagram carries it. Default true.',
51
+ },
52
+ maxSteps: {
53
+ type: 'integer',
54
+ description: 'Cap on the sequence trace length when rendering kind=sequence. Default 60.',
55
+ },
56
+ },
57
+ output: {
58
+ schema: {
59
+ type: 'json',
60
+ description: 'Render result (diagram source), parse result (LogicModelV1 + labels), or the modelling review report with findings, round-trip diff and next steps.',
61
+ },
62
+ render(_args, value) {
63
+ const record = value as unknown as Record<string, unknown>
64
+ // A rendered diagram is source text: print it verbatim so it can be copied
65
+ // into a file, then the structured result (warnings, notation, kind).
66
+ if (typeof record.diagram === 'string') {
67
+ return [
68
+ { type: 'text' as const, text: record.diagram },
69
+ { type: 'text' as const, text: JSON.stringify(value, null, 2) },
70
+ ]
71
+ }
72
+ return [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }]
73
+ },
74
+ },
75
+ timeoutMs: 10_000,
76
+ isConcurrencySafe: () => true,
77
+ async execute(args) {
78
+ try {
79
+ if (args.action === 'render') {
80
+ if (args.model === undefined) return errorResult('action=render needs `model` (a LogicModelV1 object)')
81
+ const result = renderUml(args.model, (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation) as UmlNotation, (args.kind ?? 'state') as UmlDiagram, args.maxSteps)
82
+ return { ok: true, action: 'render', notation: result.notation, kind: result.diagram, diagram: result.primary, warnings: result.warnings } as unknown as JsonValue
83
+ }
84
+ if (args.action === 'parse') {
85
+ if (args.diagram === undefined) return errorResult('action=parse needs `diagram` (Mermaid or PlantUML text)')
86
+ const result = parseUml(args.diagram, args.notation ?? 'auto')
87
+ return { ok: true, action: 'parse', notation: result.notation, kind: result.diagram, model: result.model, labels: result.labels, warnings: result.warnings } as unknown as JsonValue
88
+ }
89
+ const report = reviewUml({
90
+ model: args.model,
91
+ diagram: args.diagram,
92
+ notation: args.notation ?? 'auto',
93
+ diagramKind: (args.kind ?? 'state') as UmlDiagram,
94
+ roundTrip: args.roundTrip,
95
+ maxSteps: args.maxSteps,
96
+ })
97
+ return report as unknown as JsonValue
98
+ } catch (error) {
99
+ return errorResult(error instanceof Error ? error.message : String(error), error instanceof UmlError) as unknown as JsonValue
100
+ }
101
+ },
102
+ })
103
+
104
+ function errorResult(message: string, isUmlError = false): JsonValue {
105
+ return { ok: false, ...(isUmlError ? { errorCode: 'UML_INPUT' } : {}), error: message } as unknown as JsonValue
106
+ }