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.
- package/README.en-US.md +17 -6
- package/README.md +17 -6
- package/lib/compose-tool.js +8 -2
- package/lib/concurrency-tool.js +8 -2
- package/lib/data-tool.js +8 -2
- package/lib/engine.js +3151 -3151
- package/lib/export-tool.js +8 -2
- package/lib/index.js +28 -40
- package/lib/report-diff-tool.js +8 -2
- package/lib/report-view.js +295 -0
- package/lib/structure-tool.js +8 -2
- package/lib/tool.js +8 -2
- package/lib/types/engine.d.ts +419 -419
- package/lib/types/index.d.ts +5 -4
- package/lib/types/report-view.d.ts +43 -0
- package/lib/types/uml.d.ts +293 -256
- package/lib/uml-tool.js +21 -14
- package/lib/uml.js +76 -12
- package/package.json +2 -2
- package/skills/logicprobe/SKILL.md +42 -6
- package/skills/logicprobe/references/dsh-model-schema.md +1 -1
- package/skills/logicprobe/references/gap-routing-guide.md +10 -9
- package/skills/logicprobe/references/logic-verification-guide.md +1 -1
- package/skills/logicprobe-concurrency/SKILL.md +2 -0
- package/skills/logicprobe-datamodel/SKILL.md +5 -0
- package/skills/logicprobe-structure/SKILL.md +1 -0
- package/skills/logicprobe-uml/SKILL.md +3 -0
- package/skills/logicprobe-uml/references/uml-modeling-guide.md +70 -2
- package/src/compose-tool.ts +8 -2
- package/src/concurrency-tool.ts +8 -2
- package/src/data-tool.ts +9 -3
- package/src/export-tool.ts +8 -2
- package/src/index.ts +28 -40
- package/src/report-diff-tool.ts +8 -2
- package/src/report-view.ts +343 -0
- package/src/structure-tool.ts +8 -2
- package/src/tool.ts +8 -2
- package/src/uml-tool.ts +21 -14
- package/src/uml.ts +94 -11
package/src/uml-tool.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
|
2
2
|
import { createHash } from 'node:crypto'
|
|
3
3
|
import type { JsonValue } from './json-value.js'
|
|
4
4
|
import { REPORT_SCHEMAS, DEFAULT_HASH_SPEC, modelHash, refusalVerdict, verdictOf, verdictOfFindings, type VerdictSummary } from './engine.js'
|
|
5
|
-
import { renderUml, parseUml, parseFindings, reviewUml, explainLabels, UmlError, type UmlDiagram, type UmlFinding, type UmlNotation, type UmlParseResult } from './uml.js'
|
|
5
|
+
import { renderUml, parseUml, parseFindings, reviewUml, explainLabels, checkUmlIntent, UmlError, type UmlDiagram, type UmlFinding, type UmlIntent, type UmlNotation, type UmlParseResult } from './uml.js'
|
|
6
|
+
import { renderReportText } from './report-view.js'
|
|
6
7
|
|
|
7
8
|
export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml'
|
|
8
9
|
|
|
@@ -21,7 +22,7 @@ export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml'
|
|
|
21
22
|
export const logicProbeUmlTool = defineTool({
|
|
22
23
|
name: LOGICPROBE_UML_TOOL_NAME,
|
|
23
24
|
description:
|
|
24
|
-
'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.',
|
|
25
|
+
'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.',
|
|
25
26
|
parameters: {
|
|
26
27
|
action: {
|
|
27
28
|
type: 'string',
|
|
@@ -47,6 +48,11 @@ export const logicProbeUmlTool = defineTool({
|
|
|
47
48
|
enum: ['state', 'activity', 'sequence'],
|
|
48
49
|
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.',
|
|
49
50
|
},
|
|
51
|
+
intent: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
enum: ['present', 'verify', 'fidelity'],
|
|
54
|
+
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.',
|
|
55
|
+
},
|
|
50
56
|
roundTrip: {
|
|
51
57
|
type: 'boolean',
|
|
52
58
|
description: 'review only: render and re-parse the model to prove the diagram carries it. Default true.',
|
|
@@ -55,23 +61,21 @@ export const logicProbeUmlTool = defineTool({
|
|
|
55
61
|
type: 'integer',
|
|
56
62
|
description: 'Cap on the sequence trace length when rendering kind=sequence. Default 60.',
|
|
57
63
|
},
|
|
64
|
+
reportView: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
enum: ['digest', 'full'],
|
|
67
|
+
description: 'digest (default) prints the diagram or report body; full prints the structured result as JSON.',
|
|
68
|
+
},
|
|
58
69
|
},
|
|
59
70
|
output: {
|
|
60
71
|
schema: {
|
|
61
72
|
type: 'json',
|
|
62
73
|
description: 'Render result (diagram source), parse result (LogicModelV1 + labels), or the modelling review report with findings, round-trip diff and next steps.',
|
|
63
74
|
},
|
|
64
|
-
render(
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
if (typeof record.diagram === 'string') {
|
|
69
|
-
return [
|
|
70
|
-
{ type: 'text' as const, text: record.diagram },
|
|
71
|
-
{ type: 'text' as const, text: JSON.stringify(value, null, 2) },
|
|
72
|
-
]
|
|
73
|
-
}
|
|
74
|
-
return [{ type: 'text' as const, text: JSON.stringify(value, null, 2) }]
|
|
75
|
+
render(args, value) {
|
|
76
|
+
// A rendered diagram is source text: the report body prints it verbatim, once.
|
|
77
|
+
// Echoing the whole result as JSON printed the same diagram a second time, escaped.
|
|
78
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
75
79
|
},
|
|
76
80
|
},
|
|
77
81
|
timeoutMs: 10_000,
|
|
@@ -80,11 +84,12 @@ export const logicProbeUmlTool = defineTool({
|
|
|
80
84
|
try {
|
|
81
85
|
if (args.action === 'render') {
|
|
82
86
|
if (args.model === undefined) return errorResult('action=render needs `model` (a LogicModelV1 object)')
|
|
83
|
-
const result = renderUml(args.model, (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation) as UmlNotation, (args.kind ?? 'state') as UmlDiagram, args.maxSteps)
|
|
87
|
+
const result = renderUml(args.model, (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation) as UmlNotation, (args.kind ?? 'state') as UmlDiagram, args.maxSteps, args.intent as UmlIntent | undefined)
|
|
84
88
|
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 } as unknown as JsonValue
|
|
85
89
|
}
|
|
86
90
|
if (args.action === 'parse') {
|
|
87
91
|
if (args.diagram === undefined) return errorResult('action=parse needs `diagram` (Mermaid or PlantUML text)')
|
|
92
|
+
checkUmlIntent(args.intent as UmlIntent | undefined, 'parse')
|
|
88
93
|
const result = parseUml(args.diagram, args.notation ?? 'auto')
|
|
89
94
|
// A diagram from another family still parses into *something*. `parseFindings`
|
|
90
95
|
// says so, and the verdict is what stops that something from being trusted:
|
|
@@ -110,6 +115,7 @@ export const logicProbeUmlTool = defineTool({
|
|
|
110
115
|
// R9: the label contract was documented in the library and in `explainLabels()`, but no
|
|
111
116
|
// tool exposed it, so it was reachable only by reading the source or the docs.
|
|
112
117
|
if (args.action === 'explain-labels') {
|
|
118
|
+
checkUmlIntent(args.intent as UmlIntent | undefined, 'labels')
|
|
113
119
|
const notation = (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation) as UmlNotation
|
|
114
120
|
return {
|
|
115
121
|
ok: true,
|
|
@@ -127,6 +133,7 @@ export const logicProbeUmlTool = defineTool({
|
|
|
127
133
|
diagramKind: (args.kind ?? 'state') as UmlDiagram,
|
|
128
134
|
roundTrip: args.roundTrip,
|
|
129
135
|
maxSteps: args.maxSteps,
|
|
136
|
+
intent: args.intent as UmlIntent | undefined,
|
|
130
137
|
})
|
|
131
138
|
return report as unknown as JsonValue
|
|
132
139
|
} catch (error) {
|
package/src/uml.ts
CHANGED
|
@@ -42,6 +42,20 @@ export const UML_NOTATIONS: readonly UmlNotation[] = ['mermaid', 'plantuml']
|
|
|
42
42
|
|
|
43
43
|
export const UML_DIAGRAMS: readonly UmlDiagram[] = ['state', 'activity', 'sequence']
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* What the caller is going to do with the artifact, declared up front.
|
|
47
|
+
*
|
|
48
|
+
* The view follows the question, not the machine, so the tool cannot pick it. What it
|
|
49
|
+
* can do is refuse a pairing that cannot serve the question — before the work, not as
|
|
50
|
+
* a finding after it.
|
|
51
|
+
*/
|
|
52
|
+
export type UmlIntent = 'present' | 'verify' | 'fidelity'
|
|
53
|
+
|
|
54
|
+
export const UML_INTENTS: readonly UmlIntent[] = ['present', 'verify', 'fidelity']
|
|
55
|
+
|
|
56
|
+
/** The tool actions, for the entry check. `labels` is `action=explain-labels`. */
|
|
57
|
+
export type UmlIntentAction = 'render' | 'parse' | 'review' | 'labels'
|
|
58
|
+
|
|
45
59
|
/** Marker every generated diagram carries; renderers ignore it, the parser uses it. */
|
|
46
60
|
const DIRECTIVE_NAMESPACE = 'logicprobe:'
|
|
47
61
|
|
|
@@ -401,26 +415,92 @@ function renderSequence(context: RenderContext, notation: UmlNotation, maxSteps:
|
|
|
401
415
|
return { text: lines.join('\n') + '\n', truncated }
|
|
402
416
|
}
|
|
403
417
|
|
|
418
|
+
/** What each action hands back, so a declared intent can be matched against it. */
|
|
419
|
+
const ACTION_PRODUCES: Record<UmlIntentAction, string> = {
|
|
420
|
+
render: 'a diagram',
|
|
421
|
+
parse: 'a LogicModelV1 read out of a diagram',
|
|
422
|
+
review: 'an audit of the modelling',
|
|
423
|
+
labels: 'the label and comment conventions the parser accepts',
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* The intents each action can serve.
|
|
428
|
+
*
|
|
429
|
+
* `fidelity` is a review job: render produces a diagram, and a diagram nothing has
|
|
430
|
+
* compared back to its model proves nothing about it.
|
|
431
|
+
*/
|
|
432
|
+
const ACTION_INTENTS: Record<UmlIntentAction, readonly UmlIntent[]> = {
|
|
433
|
+
render: ['present', 'verify'],
|
|
434
|
+
parse: ['verify'],
|
|
435
|
+
review: ['present', 'verify', 'fidelity'],
|
|
436
|
+
labels: [],
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* The `(notation, view)` cells this module can draw.
|
|
441
|
+
*
|
|
442
|
+
* PlantUML activity is the cell that does not exist: its activity syntax is a
|
|
443
|
+
* structured flowchart language, so a graph with merges or cycles needs a while/if
|
|
444
|
+
* reconstruction this module does not perform. Refusing is the honest outcome —
|
|
445
|
+
* quietly emitting a state diagram under an "activity" request would mislabel the
|
|
446
|
+
* model.
|
|
447
|
+
*/
|
|
448
|
+
export function assertUmlView(notation: UmlNotation, diagram: UmlDiagram): void {
|
|
449
|
+
if (!UML_NOTATIONS.includes(notation)) throw new UmlError('unknown notation "' + String(notation) + '"; expected ' + UML_NOTATIONS.join(' | '))
|
|
450
|
+
if (!UML_DIAGRAMS.includes(diagram)) throw new UmlError('unknown diagram "' + String(diagram) + '"; expected ' + UML_DIAGRAMS.join(' | '))
|
|
451
|
+
if (notation === 'plantuml' && diagram === 'activity') {
|
|
452
|
+
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')
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Refuse, before any work, a request whose declared purpose the view cannot serve.
|
|
458
|
+
*
|
|
459
|
+
* Without an intent this changes nothing: the review still reports `UML019` when the
|
|
460
|
+
* fidelity check does not apply to the view it was given. With one, an impossible
|
|
461
|
+
* pairing is an error carrying a code, instead of a report about the wrong artifact —
|
|
462
|
+
* which is the failure this check exists to prevent.
|
|
463
|
+
*
|
|
464
|
+
* @param intent - the declared purpose, or `undefined` to leave the behaviour unchanged.
|
|
465
|
+
* @param action - which action is about to run.
|
|
466
|
+
* @param view - the requested `(notation, diagram)`, for the actions that have one.
|
|
467
|
+
*/
|
|
468
|
+
export function checkUmlIntent(intent: UmlIntent | undefined, action: UmlIntentAction, view?: { notation: UmlNotation; diagram: UmlDiagram }): void {
|
|
469
|
+
if (intent === undefined) return
|
|
470
|
+
if (!UML_INTENTS.includes(intent)) throw new UmlError('unknown intent "' + String(intent) + '"; expected ' + UML_INTENTS.join(' | '))
|
|
471
|
+
const served = ACTION_INTENTS[action]
|
|
472
|
+
if (!served.includes(intent)) {
|
|
473
|
+
throw new UmlError(
|
|
474
|
+
'intent "' + intent + '" cannot be served by action=' + action + ', which returns ' + ACTION_PRODUCES[action] + '; it serves ' +
|
|
475
|
+
(served.length === 0 ? 'no intent — leave `intent` off' : served.join(' | ')),
|
|
476
|
+
'UML_INTENT_MISMATCH')
|
|
477
|
+
}
|
|
478
|
+
if (view === undefined) return
|
|
479
|
+
assertUmlView(view.notation, view.diagram)
|
|
480
|
+
if (intent === 'present' || view.diagram !== 'sequence') return
|
|
481
|
+
throw new UmlError(
|
|
482
|
+
'intent "' + intent + '" needs a machine, and a sequence view is one BFS trace: ' +
|
|
483
|
+
(intent === 'fidelity'
|
|
484
|
+
? '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".'
|
|
485
|
+
: '`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".'),
|
|
486
|
+
'UML_INTENT_UNSERVABLE')
|
|
487
|
+
}
|
|
488
|
+
|
|
404
489
|
/**
|
|
405
490
|
* Render a LogicModelV1 as UML.
|
|
406
491
|
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* while/if reconstruction this module does not perform. Refusing is the honest
|
|
410
|
-
* outcome — quietly emitting a state diagram under an "activity" request would
|
|
411
|
-
* mislabel the model. Mermaid covers all three views.
|
|
492
|
+
* Mermaid covers all three views; PlantUML covers state and sequence (see
|
|
493
|
+
* `assertUmlView` for why activity is refused).
|
|
412
494
|
*
|
|
413
495
|
* @param input - candidate LogicModelV1.
|
|
414
496
|
* @param notation - `mermaid` (default) or `plantuml`.
|
|
415
497
|
* @param diagram - `state` (default), `activity`, or `sequence`.
|
|
416
498
|
* @param maxSteps - cap on the sequence trace length.
|
|
499
|
+
* @param intent - what the diagram is for; a view that cannot serve it is refused first.
|
|
417
500
|
*/
|
|
418
|
-
export function renderUml(input: unknown, notation: UmlNotation = 'mermaid', diagram: UmlDiagram = 'state', maxSteps = 60): UmlRenderResult {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (notation === 'plantuml' && diagram === 'activity') {
|
|
422
|
-
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"')
|
|
423
|
-
}
|
|
501
|
+
export function renderUml(input: unknown, notation: UmlNotation = 'mermaid', diagram: UmlDiagram = 'state', maxSteps = 60, intent?: UmlIntent): UmlRenderResult {
|
|
502
|
+
assertUmlView(notation, diagram)
|
|
503
|
+
checkUmlIntent(intent, 'render', { notation, diagram })
|
|
424
504
|
const context = prepareRender(input)
|
|
425
505
|
const warnings = [...context.warnings]
|
|
426
506
|
let primary: string
|
|
@@ -1524,6 +1604,8 @@ export interface UmlReviewOptions {
|
|
|
1524
1604
|
roundTrip?: boolean
|
|
1525
1605
|
/** Cap on the sequence trace used for the fidelity check. */
|
|
1526
1606
|
maxSteps?: number
|
|
1607
|
+
/** What the review is for; a view that cannot serve it is refused before any work. */
|
|
1608
|
+
intent?: UmlIntent
|
|
1527
1609
|
}
|
|
1528
1610
|
|
|
1529
1611
|
/**
|
|
@@ -1549,6 +1631,7 @@ export function reviewUml(options: UmlReviewOptions): UmlReviewReport {
|
|
|
1549
1631
|
|
|
1550
1632
|
let notation: UmlNotation = options.notation === undefined || options.notation === 'auto' ? 'mermaid' : options.notation
|
|
1551
1633
|
const kind: UmlDiagram = options.diagramKind ?? 'state'
|
|
1634
|
+
checkUmlIntent(options.intent, 'review', { notation, diagram: kind })
|
|
1552
1635
|
let model: LogicModelV1 | undefined
|
|
1553
1636
|
let labels: Record<string, string> | undefined
|
|
1554
1637
|
let parsed: UmlParseResult | undefined
|