dsh-logicprobe 1.0.0 → 1.1.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/README.en-US.md +80 -83
- package/README.md +80 -83
- package/lib/engine.js +3151 -3101
- package/lib/types/engine.d.ts +419 -414
- package/lib/uml-tool.js +146 -133
- package/lib/uml.js +1748 -1738
- package/package.json +1 -1
- package/skills/logicprobe/SKILL.md +3 -3
- package/skills/logicprobe/references/dsh-model-schema.md +24 -3
- package/skills/logicprobe/references/hash-spec.md +25 -0
- package/skills/logicprobe-uml/SKILL.md +3 -1
- package/skills/logicprobe-uml/references/uml-modeling-guide.md +7 -6
- package/src/engine.ts +67 -10
- package/src/uml-tool.ts +17 -4
- package/src/uml.ts +20 -7
package/lib/uml-tool.js
CHANGED
|
@@ -1,133 +1,146 @@
|
|
|
1
|
-
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { REPORT_SCHEMAS, DEFAULT_HASH_SPEC, modelHash, refusalVerdict, verdictOf, verdictOfFindings } from './engine.js';
|
|
4
|
-
import { renderUml, parseUml, parseFindings, reviewUml, UmlError } from './uml.js';
|
|
5
|
-
export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml';
|
|
6
|
-
/**
|
|
7
|
-
* DSH tool wrapping the UML front end: model a code flow as a UML diagram
|
|
8
|
-
* (`action: "render"`), read a UML diagram back into a LogicModelV1
|
|
9
|
-
* (`action: "parse"`), or review the modelling itself (`action: "review"`).
|
|
10
|
-
*
|
|
11
|
-
* review is the half that makes the feature a check rather than a drawing
|
|
12
|
-
* utility: it reports structural defects the diagram would present as valid
|
|
13
|
-
* flow (unreachable states, dead ends, ambiguous and non-exhaustive branches,
|
|
14
|
-
* self-loops with no exit), documentation gaps (a symbol no reader can map back
|
|
15
|
-
* to code), and — the fidelity check — whether the rendered diagram reads back
|
|
16
|
-
* as the model it was drawn from.
|
|
17
|
-
*/
|
|
18
|
-
export const logicProbeUmlTool = defineTool({
|
|
19
|
-
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. 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
|
-
parameters: {
|
|
22
|
-
action: {
|
|
23
|
-
type: 'string',
|
|
24
|
-
required: true,
|
|
25
|
-
enum: ['render', 'parse', 'review'],
|
|
26
|
-
description: 'render = model → UML source; parse = UML source → model; review = audit the modelling (and its fidelity to the model).',
|
|
27
|
-
},
|
|
28
|
-
model: {
|
|
29
|
-
type: 'json',
|
|
30
|
-
description: 'LogicModelV1 machine. Required for render; accepted by review (alone, or next to diagram to check the two against each other).',
|
|
31
|
-
},
|
|
32
|
-
diagram: {
|
|
33
|
-
type: 'string',
|
|
34
|
-
description: 'UML source text. Required for parse; accepted by review.',
|
|
35
|
-
},
|
|
36
|
-
notation: {
|
|
37
|
-
type: 'string',
|
|
38
|
-
enum: ['auto', 'mermaid', 'plantuml'],
|
|
39
|
-
description: 'Diagram language. Default auto: detected from the text for parse/review, mermaid for render.',
|
|
40
|
-
},
|
|
41
|
-
kind: {
|
|
42
|
-
type: 'string',
|
|
43
|
-
enum: ['state', 'activity', 'sequence'],
|
|
44
|
-
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
|
-
roundTrip: {
|
|
47
|
-
type: 'boolean',
|
|
48
|
-
description: 'review only: render and re-parse the model to prove the diagram carries it. Default true.',
|
|
49
|
-
},
|
|
50
|
-
maxSteps: {
|
|
51
|
-
type: 'integer',
|
|
52
|
-
description: 'Cap on the sequence trace length when rendering kind=sequence. Default 60.',
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
output: {
|
|
56
|
-
schema: {
|
|
57
|
-
type: 'json',
|
|
58
|
-
description: 'Render result (diagram source), parse result (LogicModelV1 + labels), or the modelling review report with findings, round-trip diff and next steps.',
|
|
59
|
-
},
|
|
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
|
-
},
|
|
72
|
-
},
|
|
73
|
-
timeoutMs: 10_000,
|
|
74
|
-
isConcurrencySafe: () => true,
|
|
75
|
-
async execute(args) {
|
|
76
|
-
try {
|
|
77
|
-
if (args.action === 'render') {
|
|
78
|
-
if (args.model === undefined)
|
|
79
|
-
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);
|
|
81
|
-
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
|
-
}
|
|
83
|
-
if (args.action === 'parse') {
|
|
84
|
-
if (args.diagram === undefined)
|
|
85
|
-
return errorResult('action=parse needs `diagram` (Mermaid or PlantUML text)');
|
|
86
|
-
const result = parseUml(args.diagram, args.notation ?? 'auto');
|
|
87
|
-
// A diagram from another family still parses into *something*. `parseFindings`
|
|
88
|
-
// says so, and the verdict is what stops that something from being trusted:
|
|
89
|
-
// `ok: true` only means the parser ran.
|
|
90
|
-
const findings = parseFindings(result);
|
|
91
|
-
return {
|
|
92
|
-
ok: true,
|
|
93
|
-
ran: true,
|
|
94
|
-
...parseVerdict(result, findings),
|
|
95
|
-
schema: REPORT_SCHEMAS.umlParse,
|
|
96
|
-
action: 'parse',
|
|
97
|
-
notation: result.notation,
|
|
98
|
-
kind: result.diagram,
|
|
99
|
-
model: result.model,
|
|
100
|
-
labels: result.labels,
|
|
101
|
-
findings,
|
|
102
|
-
discardedConstructs: result.discardedConstructs,
|
|
103
|
-
discardedEdges: result.discardedEdges,
|
|
104
|
-
hashes: { hashSpec: DEFAULT_HASH_SPEC, modelHash: modelHash(result.model), diagram: createHash('sha256').update(args.diagram).digest('hex') },
|
|
105
|
-
warnings: result.warnings,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
notation
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
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';
|
|
5
|
+
export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml';
|
|
6
|
+
/**
|
|
7
|
+
* DSH tool wrapping the UML front end: model a code flow as a UML diagram
|
|
8
|
+
* (`action: "render"`), read a UML diagram back into a LogicModelV1
|
|
9
|
+
* (`action: "parse"`), or review the modelling itself (`action: "review"`).
|
|
10
|
+
*
|
|
11
|
+
* review is the half that makes the feature a check rather than a drawing
|
|
12
|
+
* utility: it reports structural defects the diagram would present as valid
|
|
13
|
+
* flow (unreachable states, dead ends, ambiguous and non-exhaustive branches,
|
|
14
|
+
* self-loops with no exit), documentation gaps (a symbol no reader can map back
|
|
15
|
+
* to code), and — the fidelity check — whether the rendered diagram reads back
|
|
16
|
+
* as the model it was drawn from.
|
|
17
|
+
*/
|
|
18
|
+
export const logicProbeUmlTool = defineTool({
|
|
19
|
+
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
|
+
parameters: {
|
|
22
|
+
action: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
required: true,
|
|
25
|
+
enum: ['render', 'parse', 'review', 'explain-labels'],
|
|
26
|
+
description: 'render = model → UML source; parse = UML source → model; review = audit the modelling (and its fidelity to the model); explain-labels = print the label and comment conventions this parser accepts.',
|
|
27
|
+
},
|
|
28
|
+
model: {
|
|
29
|
+
type: 'json',
|
|
30
|
+
description: 'LogicModelV1 machine. Required for render; accepted by review (alone, or next to diagram to check the two against each other).',
|
|
31
|
+
},
|
|
32
|
+
diagram: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'UML source text. Required for parse; accepted by review.',
|
|
35
|
+
},
|
|
36
|
+
notation: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: ['auto', 'mermaid', 'plantuml'],
|
|
39
|
+
description: 'Diagram language. Default auto: detected from the text for parse/review, mermaid for render.',
|
|
40
|
+
},
|
|
41
|
+
kind: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
enum: ['state', 'activity', 'sequence'],
|
|
44
|
+
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
|
+
roundTrip: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
description: 'review only: render and re-parse the model to prove the diagram carries it. Default true.',
|
|
49
|
+
},
|
|
50
|
+
maxSteps: {
|
|
51
|
+
type: 'integer',
|
|
52
|
+
description: 'Cap on the sequence trace length when rendering kind=sequence. Default 60.',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
output: {
|
|
56
|
+
schema: {
|
|
57
|
+
type: 'json',
|
|
58
|
+
description: 'Render result (diagram source), parse result (LogicModelV1 + labels), or the modelling review report with findings, round-trip diff and next steps.',
|
|
59
|
+
},
|
|
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
|
+
},
|
|
72
|
+
},
|
|
73
|
+
timeoutMs: 10_000,
|
|
74
|
+
isConcurrencySafe: () => true,
|
|
75
|
+
async execute(args) {
|
|
76
|
+
try {
|
|
77
|
+
if (args.action === 'render') {
|
|
78
|
+
if (args.model === undefined)
|
|
79
|
+
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);
|
|
81
|
+
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
|
+
}
|
|
83
|
+
if (args.action === 'parse') {
|
|
84
|
+
if (args.diagram === undefined)
|
|
85
|
+
return errorResult('action=parse needs `diagram` (Mermaid or PlantUML text)');
|
|
86
|
+
const result = parseUml(args.diagram, args.notation ?? 'auto');
|
|
87
|
+
// A diagram from another family still parses into *something*. `parseFindings`
|
|
88
|
+
// says so, and the verdict is what stops that something from being trusted:
|
|
89
|
+
// `ok: true` only means the parser ran.
|
|
90
|
+
const findings = parseFindings(result);
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
ran: true,
|
|
94
|
+
...parseVerdict(result, findings),
|
|
95
|
+
schema: REPORT_SCHEMAS.umlParse,
|
|
96
|
+
action: 'parse',
|
|
97
|
+
notation: result.notation,
|
|
98
|
+
kind: result.diagram,
|
|
99
|
+
model: result.model,
|
|
100
|
+
labels: result.labels,
|
|
101
|
+
findings,
|
|
102
|
+
discardedConstructs: result.discardedConstructs,
|
|
103
|
+
discardedEdges: result.discardedEdges,
|
|
104
|
+
hashes: { hashSpec: DEFAULT_HASH_SPEC, modelHash: modelHash(result.model), diagram: createHash('sha256').update(args.diagram).digest('hex') },
|
|
105
|
+
warnings: result.warnings,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// R9: the label contract was documented in the library and in `explainLabels()`, but no
|
|
109
|
+
// tool exposed it, so it was reachable only by reading the source or the docs.
|
|
110
|
+
if (args.action === 'explain-labels') {
|
|
111
|
+
const notation = (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation);
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
ran: true,
|
|
115
|
+
...verdictOf(0, 0),
|
|
116
|
+
schema: REPORT_SCHEMAS.umlExplainLabels,
|
|
117
|
+
action: 'explain-labels',
|
|
118
|
+
...explainLabels(notation),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const report = reviewUml({
|
|
122
|
+
model: args.model,
|
|
123
|
+
diagram: args.diagram,
|
|
124
|
+
notation: args.notation ?? 'auto',
|
|
125
|
+
diagramKind: (args.kind ?? 'state'),
|
|
126
|
+
roundTrip: args.roundTrip,
|
|
127
|
+
maxSteps: args.maxSteps,
|
|
128
|
+
});
|
|
129
|
+
return report;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
return errorResult(message, error instanceof UmlError ? error.code : undefined);
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
/** The parse verdict counts parser notes as findings: an ignored line is information the model does not carry. */
|
|
138
|
+
function parseVerdict(result, findings) {
|
|
139
|
+
const errors = findings.filter((finding) => finding.severity === 'error').length;
|
|
140
|
+
const warnings = findings.filter((finding) => finding.severity === 'warning').length + result.warnings.length;
|
|
141
|
+
const firstCode = findings.find((finding) => finding.severity === 'error')?.code ?? findings.find((finding) => finding.severity === 'warning')?.code;
|
|
142
|
+
return verdictOf(errors, warnings, firstCode);
|
|
143
|
+
}
|
|
144
|
+
function errorResult(message, errorCode = 'UML_INPUT') {
|
|
145
|
+
return { ok: false, ran: false, ...refusalVerdict(message), errorCode, error: message };
|
|
146
|
+
}
|