dsh-logicprobe 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/uml-tool.js CHANGED
@@ -1,153 +1,153 @@
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, checkUmlIntent, UmlError } from './uml.js';
5
- import { renderReportText } from './report-view.js';
6
- export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml';
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: '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.',
22
- parameters: {
23
- action: {
24
- type: 'string',
25
- required: true,
26
- enum: ['render', 'parse', 'review', 'explain-labels'],
27
- 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.',
28
- },
29
- model: {
30
- type: 'json',
31
- description: 'LogicModelV1 machine. Required for render; accepted by review (alone, or next to diagram to check the two against each other).',
32
- },
33
- diagram: {
34
- type: 'string',
35
- description: 'UML source text. Required for parse; accepted by review.',
36
- },
37
- notation: {
38
- type: 'string',
39
- enum: ['auto', 'mermaid', 'plantuml'],
40
- description: 'Diagram language. Default auto: detected from the text for parse/review, mermaid for render.',
41
- },
42
- kind: {
43
- type: 'string',
44
- enum: ['state', 'activity', 'sequence'],
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.',
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
- },
52
- roundTrip: {
53
- type: 'boolean',
54
- description: 'review only: render and re-parse the model to prove the diagram carries it. Default true.',
55
- },
56
- maxSteps: {
57
- type: 'integer',
58
- description: 'Cap on the sequence trace length when rendering kind=sequence. Default 60.',
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
- },
65
- },
66
- output: {
67
- schema: {
68
- type: 'json',
69
- description: 'Render result (diagram source), parse result (LogicModelV1 + labels), or the modelling review report with findings, round-trip diff and next steps.',
70
- },
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' }) }];
75
- },
76
- },
77
- timeoutMs: 10_000,
78
- isConcurrencySafe: () => true,
79
- async execute(args) {
80
- try {
81
- if (args.action === 'render') {
82
- if (args.model === undefined)
83
- return errorResult('action=render needs `model` (a LogicModelV1 object)');
84
- const result = renderUml(args.model, (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation), (args.kind ?? 'state'), args.maxSteps, args.intent);
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 };
86
- }
87
- if (args.action === 'parse') {
88
- if (args.diagram === undefined)
89
- return errorResult('action=parse needs `diagram` (Mermaid or PlantUML text)');
90
- checkUmlIntent(args.intent, 'parse');
91
- const result = parseUml(args.diagram, args.notation ?? 'auto');
92
- // A diagram from another family still parses into *something*. `parseFindings`
93
- // says so, and the verdict is what stops that something from being trusted:
94
- // `ok: true` only means the parser ran.
95
- const findings = parseFindings(result);
96
- return {
97
- ok: true,
98
- ran: true,
99
- ...parseVerdict(result, findings),
100
- schema: REPORT_SCHEMAS.umlParse,
101
- action: 'parse',
102
- notation: result.notation,
103
- kind: result.diagram,
104
- model: result.model,
105
- labels: result.labels,
106
- findings,
107
- discardedConstructs: result.discardedConstructs,
108
- discardedEdges: result.discardedEdges,
109
- hashes: { hashSpec: DEFAULT_HASH_SPEC, modelHash: modelHash(result.model), diagram: createHash('sha256').update(args.diagram).digest('hex') },
110
- warnings: result.warnings,
111
- };
112
- }
113
- // R9: the label contract was documented in the library and in `explainLabels()`, but no
114
- // tool exposed it, so it was reachable only by reading the source or the docs.
115
- if (args.action === 'explain-labels') {
116
- checkUmlIntent(args.intent, 'labels');
117
- const notation = (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation);
118
- return {
119
- ok: true,
120
- ran: true,
121
- ...verdictOf(0, 0),
122
- schema: REPORT_SCHEMAS.umlExplainLabels,
123
- action: 'explain-labels',
124
- ...explainLabels(notation),
125
- };
126
- }
127
- const report = reviewUml({
128
- model: args.model,
129
- diagram: args.diagram,
130
- notation: args.notation ?? 'auto',
131
- diagramKind: (args.kind ?? 'state'),
132
- roundTrip: args.roundTrip,
133
- maxSteps: args.maxSteps,
134
- intent: args.intent,
135
- });
136
- return report;
137
- }
138
- catch (error) {
139
- const message = error instanceof Error ? error.message : String(error);
140
- return errorResult(message, error instanceof UmlError ? error.code : undefined);
141
- }
142
- },
143
- });
144
- /** The parse verdict counts parser notes as findings: an ignored line is information the model does not carry. */
145
- function parseVerdict(result, findings) {
146
- const errors = findings.filter((finding) => finding.severity === 'error').length;
147
- const warnings = findings.filter((finding) => finding.severity === 'warning').length + result.warnings.length;
148
- const firstCode = findings.find((finding) => finding.severity === 'error')?.code ?? findings.find((finding) => finding.severity === 'warning')?.code;
149
- return verdictOf(errors, warnings, firstCode);
150
- }
151
- function errorResult(message, errorCode = 'UML_INPUT') {
152
- return { ok: false, ran: false, ...refusalVerdict(message), errorCode, error: message };
153
- }
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, checkUmlIntent, UmlError } from './uml.js';
5
+ import { renderReportText } from './report-view.js';
6
+ export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml';
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: '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.',
22
+ parameters: {
23
+ action: {
24
+ type: 'string',
25
+ required: true,
26
+ enum: ['render', 'parse', 'review', 'explain-labels'],
27
+ 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.',
28
+ },
29
+ model: {
30
+ type: 'json',
31
+ description: 'LogicModelV1 machine. Required for render; accepted by review (alone, or next to diagram to check the two against each other).',
32
+ },
33
+ diagram: {
34
+ type: 'string',
35
+ description: 'UML source text. Required for parse; accepted by review.',
36
+ },
37
+ notation: {
38
+ type: 'string',
39
+ enum: ['auto', 'mermaid', 'plantuml'],
40
+ description: 'Diagram language. Default auto: detected from the text for parse/review, mermaid for render.',
41
+ },
42
+ kind: {
43
+ type: 'string',
44
+ enum: ['state', 'activity', 'sequence'],
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.',
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
+ },
52
+ roundTrip: {
53
+ type: 'boolean',
54
+ description: 'review only: render and re-parse the model to prove the diagram carries it. Default true.',
55
+ },
56
+ maxSteps: {
57
+ type: 'integer',
58
+ description: 'Cap on the sequence trace length when rendering kind=sequence. Default 60.',
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
+ },
65
+ },
66
+ output: {
67
+ schema: {
68
+ type: 'json',
69
+ description: 'Render result (diagram source), parse result (LogicModelV1 + labels), or the modelling review report with findings, round-trip diff and next steps.',
70
+ },
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' }) }];
75
+ },
76
+ },
77
+ timeoutMs: 10_000,
78
+ isConcurrencySafe: () => true,
79
+ async execute(args) {
80
+ try {
81
+ if (args.action === 'render') {
82
+ if (args.model === undefined)
83
+ return errorResult('action=render needs `model` (a LogicModelV1 object)');
84
+ const result = renderUml(args.model, (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation), (args.kind ?? 'state'), args.maxSteps, args.intent);
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 };
86
+ }
87
+ if (args.action === 'parse') {
88
+ if (args.diagram === undefined)
89
+ return errorResult('action=parse needs `diagram` (Mermaid or PlantUML text)');
90
+ checkUmlIntent(args.intent, 'parse');
91
+ const result = parseUml(args.diagram, args.notation ?? 'auto');
92
+ // A diagram from another family still parses into *something*. `parseFindings`
93
+ // says so, and the verdict is what stops that something from being trusted:
94
+ // `ok: true` only means the parser ran.
95
+ const findings = parseFindings(result);
96
+ return {
97
+ ok: true,
98
+ ran: true,
99
+ ...parseVerdict(result, findings),
100
+ schema: REPORT_SCHEMAS.umlParse,
101
+ action: 'parse',
102
+ notation: result.notation,
103
+ kind: result.diagram,
104
+ model: result.model,
105
+ labels: result.labels,
106
+ findings,
107
+ discardedConstructs: result.discardedConstructs,
108
+ discardedEdges: result.discardedEdges,
109
+ hashes: { hashSpec: DEFAULT_HASH_SPEC, modelHash: modelHash(result.model), diagram: createHash('sha256').update(args.diagram).digest('hex') },
110
+ warnings: result.warnings,
111
+ };
112
+ }
113
+ // R9: the label contract was documented in the library and in `explainLabels()`, but no
114
+ // tool exposed it, so it was reachable only by reading the source or the docs.
115
+ if (args.action === 'explain-labels') {
116
+ checkUmlIntent(args.intent, 'labels');
117
+ const notation = (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation);
118
+ return {
119
+ ok: true,
120
+ ran: true,
121
+ ...verdictOf(0, 0),
122
+ schema: REPORT_SCHEMAS.umlExplainLabels,
123
+ action: 'explain-labels',
124
+ ...explainLabels(notation),
125
+ };
126
+ }
127
+ const report = reviewUml({
128
+ model: args.model,
129
+ diagram: args.diagram,
130
+ notation: args.notation ?? 'auto',
131
+ diagramKind: (args.kind ?? 'state'),
132
+ roundTrip: args.roundTrip,
133
+ maxSteps: args.maxSteps,
134
+ intent: args.intent,
135
+ });
136
+ return report;
137
+ }
138
+ catch (error) {
139
+ const message = error instanceof Error ? error.message : String(error);
140
+ return errorResult(message, error instanceof UmlError ? error.code : undefined);
141
+ }
142
+ },
143
+ });
144
+ /** The parse verdict counts parser notes as findings: an ignored line is information the model does not carry. */
145
+ function parseVerdict(result, findings) {
146
+ const errors = findings.filter((finding) => finding.severity === 'error').length;
147
+ const warnings = findings.filter((finding) => finding.severity === 'warning').length + result.warnings.length;
148
+ const firstCode = findings.find((finding) => finding.severity === 'error')?.code ?? findings.find((finding) => finding.severity === 'warning')?.code;
149
+ return verdictOf(errors, warnings, firstCode);
150
+ }
151
+ function errorResult(message, errorCode = 'UML_INPUT') {
152
+ return { ok: false, ran: false, ...refusalVerdict(message), errorCode, error: message };
153
+ }
package/lib/uml.js CHANGED
@@ -618,7 +618,7 @@ function parseTransitionLabel(label, warnings) {
618
618
  * comes out would be presented as if it described the file.
619
619
  */
620
620
  const MERMAID_OTHER_FAMILIES = [
621
- 'classDiagram', 'erDiagram', 'gantt', 'pie', 'journey', 'mindmap', 'gitGraph',
621
+ 'classDiagram', 'erDiagram', 'gantt', 'pie', 'journey', 'mindmap', 'gitGraph', 'usecase-beta',
622
622
  'C4Context', 'C4Container', 'C4Component', 'C4Dynamic', 'C4Deployment',
623
623
  'requirementDiagram', 'timeline', 'quadrantChart', 'sankey-beta', 'block-beta',
624
624
  'packet-beta', 'architecture-beta', 'radar-beta', 'treemap-beta', 'xychart-beta',
@@ -642,6 +642,12 @@ function detectNotation(text) {
642
642
  return 'mermaid';
643
643
  throw new UmlError('cannot tell whether this is Mermaid or PlantUML text: expected `stateDiagram-v2` / `flowchart` / `sequenceDiagram`, or `@startuml`');
644
644
  }
645
+ /**
646
+ * PlantUML use case syntax that carries no keyword: a colon actor, or a parenthesised
647
+ * use case, with an optional alias and an optional relation on the same line.
648
+ * `:User: --> (Login)` and `(Login) as (L)` are the whole header of such a file.
649
+ */
650
+ const USECASE_SYNTAX = /^[ \t]*(?::[^:\n]+:(?:[ \t]+as[ \t]+[A-Za-z_][A-Za-z0-9_]*)?|\([^()\n]+\)(?:[ \t]+as[ \t]+\(?[^()\n]+\)?)?)[ \t]*(?:$|-->|->|\.\.>|\.>|--\|>|<\|--|--)/m;
645
651
  function detectDiagram(text) {
646
652
  if (/^\s*stateDiagram/m.test(text))
647
653
  return 'state';
@@ -658,6 +664,13 @@ function detectDiagram(text) {
658
664
  // structural one logicprobe emits, everything else in that family is a state diagram too.
659
665
  if (/^\s*participant\b/m.test(text) || /->>\s*/.test(text))
660
666
  return 'sequence';
667
+ // A use case diagram has no keyword header at all. The state parser below would read
668
+ // its arrows as transitions and return a plausible machine with no finding, so the
669
+ // family is refused by name. Its own model, and the tool that reads it, live in the
670
+ // dsh-model-everything plugin.
671
+ if (USECASE_SYNTAX.test(text)) {
672
+ throw new UmlError('this text is a use case diagram (`:Actor: --> (UseCase)`, `(UseCase) as (U)`), which this plugin does not model: it models state machines and flows, and reading it as one returns a machine the diagram never drew. The dsh-model-everything plugin renders, reads and reviews use case diagrams; keep this diagram as its own view.', 'UML_NOT_A_STATE_DIAGRAM');
673
+ }
661
674
  return 'state';
662
675
  }
663
676
  throw new UmlError('cannot tell which diagram kind this text declares');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-logicprobe",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
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",
@@ -40,7 +40,7 @@
40
40
  ]
41
41
  },
42
42
  "compatibility": {
43
- "dsh": "^0.1.0-rc.7 || ^0.1.1-rc.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.3-alpha.2 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ^0.1.5-alpha.2 || ^0.1.5-rc.2 || ^0.1.5-rc.3 || ^0.1.6-alpha.1 || ^0.1.6-alpha.2 || ^0.1.7-alpha.1 || ^0.1.7-alpha.2 || ^0.1.7-rc.1 || ^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
43
+ "dsh": "^0.1.0-rc.7 || ^0.1.1-rc.1 || ^0.1.2-alpha.2 || ^0.1.2-alpha.3 || ^0.1.2-alpha.4 || ^0.1.2-alpha.5 || ^0.1.2-rc.1 || ^0.1.3-alpha.1 || ^0.1.3-alpha.2 || ^0.1.5-alpha.1 || ^0.1.5-rc.1 || ^0.1.5-alpha.2 || ^0.1.5-rc.2 || ^0.1.5-rc.3 || ^0.1.6-alpha.1 || ^0.1.6-alpha.2 || ^0.1.7-alpha.1 || ^0.1.7-alpha.2 || ^0.1.7-rc.1 || ^0.1.7-rc.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2 || ^0.2.1-rc.1 || ^0.2.1-rc.2",
44
44
  "dshReleases": {
45
45
  "0.1.0-rc.7": "compatible",
46
46
  "0.1.0-rc.8": "compatible",
@@ -67,7 +67,9 @@
67
67
  "0.2.0-rc.1": "compatible",
68
68
  "0.2.0-rc.2": "compatible",
69
69
  "0.2.1-alpha.1": "compatible",
70
- "0.2.1-alpha.2": "compatible"
70
+ "0.2.1-alpha.2": "compatible",
71
+ "0.2.1-rc.1": "compatible",
72
+ "0.2.1-rc.2": "compatible"
71
73
  },
72
74
  "profiles": [
73
75
  "headless",
@@ -85,11 +87,11 @@
85
87
  },
86
88
  "peerDependencies": {
87
89
  "@deepseek-ai/cordis": "^4.0.4",
88
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
89
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
90
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
91
- "@deepseek-ai/dsh-skill-filesystem": "^0.1.0-rc.8 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
92
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2",
90
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2 || ^0.2.1-rc.1 || ^0.2.1-rc.2",
91
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2 || ^0.2.1-rc.1 || ^0.2.1-rc.2",
92
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2 || ^0.2.1-rc.1 || ^0.2.1-rc.2",
93
+ "@deepseek-ai/dsh-skill-filesystem": "^0.1.0-rc.8 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2 || ^0.2.1-rc.1 || ^0.2.1-rc.2",
94
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.1 || ^0.2.1-alpha.1 || ^0.2.1-alpha.2 || ^0.2.1-rc.1 || ^0.2.1-rc.2",
93
95
  "@deepseek-ai/schemastery": "^3.18.4"
94
96
  },
95
97
  "devDependencies": {
@@ -110,7 +112,7 @@
110
112
  "@deepseek-ai/dsh-tools": "^0.2.0-rc.2",
111
113
  "@deepseek-ai/dsh-user-approval": "^0.2.0-rc.2",
112
114
  "@deepseek-ai/schemastery": "^3.18.4",
113
- "@types/node": "^26.6.4",
115
+ "@types/node": "^26.6.5",
114
116
  "typescript": "^7.0.2"
115
117
  },
116
118
  "author": {
@@ -5,6 +5,15 @@ description: "Use when you want to verify something about code: a design doc, sp
5
5
 
6
6
  # Logic Probe
7
7
 
8
+ > **Successor: [`dsh-model-everything`](https://github.com/AmethystLuna/dsh-model-everything).**
9
+ > This plugin continues as `dsh-model-everything`: the same domains, a superset of the
10
+ > tooling, and a skill for each of them. If both are installed, load the
11
+ > `model-everything` skill for new work — it routes to `model-everything-uml`,
12
+ > `model-everything-structure`, `model-everything-datamodel` and
13
+ > `model-everything-interleaving` — and keep this plugin's skills as the reference for its
14
+ > own tool names (`logicprobe_*`). The two do not share a registry: a claim verified through
15
+ > one is not verified in the other.
16
+
8
17
  For an unverified design, failure is only a matter of time. Complex code becomes clear once you model it and run it. Check every claim before you act on it.
9
18
 
10
19
  ## When This Skill Applies
@@ -446,6 +455,20 @@ This skill owns behavioural claims. Three neighbouring domains have their own sk
446
455
  | Concurrency claims — "thread-safe", "lock-free", "no data race", "ISR-safe" — mined and routed to TSan/Helgrind/CBMC/TLA+, never proven here | `logicprobe-concurrency` |
447
456
  | Entities, fields, relationships, data invariants, schema migrations (DS1-DS4, DA1-DA12, DD1-DD4) | `logicprobe-datamodel` |
448
457
 
458
+ ### The Same Domains in the Successor
459
+
460
+ [dsh-model-everything](https://github.com/AmethystLuna/dsh-model-everything) covers each domain above with one skill, and adds two this plugin does not have. Use the mapping when both plugins are installed — the successor's skill is the one to load for new work, because its tools are the ones its own guidance names:
461
+
462
+ | This plugin | `dsh-model-everything` | What the successor adds |
463
+ |---|---|---|
464
+ | `logicprobe` | `model-everything` | the same doctrine over `model_verify`, `model_compose_verify` and `model_export` |
465
+ | `logicprobe-uml` | `model-everything-uml` | more diagram families (timing, profile, composite, communication, interaction overview) |
466
+ | `logicprobe-structure` | `model-everything-structure` | multi-level refinement between drawings |
467
+ | `logicprobe-datamodel` | `model-everything-datamodel` | the same DS/DA/DD families over `model_datamodel_verify` |
468
+ | `logicprobe-concurrency` | `model-everything-interleaving` | async vocabulary, a family per claim, and a lost update decided rather than routed |
469
+ | — | `model-everything-design` | layering, substitutability, interface width (no counterpart here) |
470
+ | — | `model-everything-report-reading` | how to read a report (no counterpart here) |
471
+
449
472
  ---
450
473
 
451
474
  ## Red Flags
@@ -5,6 +5,8 @@ description: "Use when a design or plan claims concurrency safety (thread-safe,
5
5
 
6
6
  # Logic Probe — Concurrency Claims
7
7
 
8
+ > **Successor: `dsh-model-everything`.** Its counterpart skill is `model-everything-interleaving`, which covers the same claims plus async and hands a shared location to `model_compose_verify` instead of routing it. Load that one when both plugins are installed; this skill stays accurate for `logicprobe_concurrency_scan`.
9
+
8
10
  This skill does **not** prove concurrency safety, and no report from it should ever be read as a proof. It mines a document, plan or code review for concurrency claims, marks them `UNVERIFIED`, and routes each one to the tool that can actually decide it.
9
11
 
10
12
  The claim-verification doctrine it inherits — enumerate verifiable claims, cite `file:line`, read `verdict` and never `ok` — lives in the `logicprobe` skill. Load that one when the task is behavioural verification of a state machine rather than a judgement about concurrency claims.
@@ -5,6 +5,8 @@ description: "Use when reviewing design docs, specs, plans or migrations claimin
5
5
 
6
6
  # Logic Probe Data
7
7
 
8
+ > **Successor: `dsh-model-everything`.** Its counterpart skill is `model-everything-datamodel`, over `model_datamodel_verify`. Load that one when both plugins are installed; this skill stays accurate for `logicprobe_datamodel_verify`.
9
+
8
10
  An unverified data model fails later. Model the schema and the migration, run them, and it becomes clear. Check every data claim before you act on it.
9
11
 
10
12
  ## Methodology
@@ -5,6 +5,8 @@ description: "Use when reviewing architecture, module structure or a dependency
5
5
 
6
6
  # Logic Probe — Structure and Dependency Review
7
7
 
8
+ > **Successor: `dsh-model-everything`.** Its counterpart skill is `model-everything-structure`, over `uml_structure_verify`, which also checks several drawings against each other. Load that one when both plugins are installed; this skill stays accurate for `logicprobe_structure_verify`.
9
+
8
10
  A component diagram is a model of an architecture, and a model can be wrong. This skill audits the graph the diagram actually draws — nodes and dependency arrows — against a declared dependency matrix, and says plainly what that does and does not prove.
9
11
 
10
12
  The claim-verification doctrine it inherits — cite evidence, read `verdict` and never `ok` — lives in the `logicprobe` skill. Load that one for behavioural claims (state machines, protocols, timing); load this one when the question is *where the dependencies point*.
@@ -5,6 +5,8 @@ description: "Use when drawing a code flow as UML or auditing a diagram someone
5
5
 
6
6
  # Logic Probe — UML Modelling
7
7
 
8
+ > **Successor: `dsh-model-everything`.** Its counterpart skill is `model-everything-uml`, which draws and reads more diagram families. Load that one when both plugins are installed; this skill stays accurate for `logicprobe_uml`.
9
+
8
10
  A diagram is a model, and a model can be wrong. A tidy flow chart is not evidence about the code, and a diagram that disagrees with its own model is worse than no diagram, because a reader will believe it.
9
11
 
10
12
  This skill covers the diagram half of the Logic Probe toolbox. The claim-verification doctrine it inherits — enumerate verifiable claims, cite `file:line`, read `verdict` and never `ok` — lives in the `logicprobe` skill; load that one when the task is to verify behaviour rather than to draw or audit a diagram.
package/src/uml.ts CHANGED
@@ -697,7 +697,7 @@ interface DiagramLine {
697
697
  * comes out would be presented as if it described the file.
698
698
  */
699
699
  const MERMAID_OTHER_FAMILIES: readonly string[] = [
700
- 'classDiagram', 'erDiagram', 'gantt', 'pie', 'journey', 'mindmap', 'gitGraph',
700
+ 'classDiagram', 'erDiagram', 'gantt', 'pie', 'journey', 'mindmap', 'gitGraph', 'usecase-beta',
701
701
  'C4Context', 'C4Container', 'C4Component', 'C4Dynamic', 'C4Deployment',
702
702
  'requirementDiagram', 'timeline', 'quadrantChart', 'sankey-beta', 'block-beta',
703
703
  'packet-beta', 'architecture-beta', 'radar-beta', 'treemap-beta', 'xychart-beta',
@@ -720,6 +720,13 @@ function detectNotation(text: string): UmlNotation {
720
720
  throw new UmlError('cannot tell whether this is Mermaid or PlantUML text: expected `stateDiagram-v2` / `flowchart` / `sequenceDiagram`, or `@startuml`')
721
721
  }
722
722
 
723
+ /**
724
+ * PlantUML use case syntax that carries no keyword: a colon actor, or a parenthesised
725
+ * use case, with an optional alias and an optional relation on the same line.
726
+ * `:User: --> (Login)` and `(Login) as (L)` are the whole header of such a file.
727
+ */
728
+ const USECASE_SYNTAX = /^[ \t]*(?::[^:\n]+:(?:[ \t]+as[ \t]+[A-Za-z_][A-Za-z0-9_]*)?|\([^()\n]+\)(?:[ \t]+as[ \t]+\(?[^()\n]+\)?)?)[ \t]*(?:$|-->|->|\.\.>|\.>|--\|>|<\|--|--)/m
729
+
723
730
  function detectDiagram(text: string): 'state' | 'activity' | 'sequence' {
724
731
  if (/^\s*stateDiagram/m.test(text)) return 'state'
725
732
  if (/^\s*(flowchart|graph)\b/m.test(text)) return 'activity'
@@ -732,6 +739,13 @@ function detectDiagram(text: string): 'state' | 'activity' | 'sequence' {
732
739
  // PlantUML declares the diagram kind by its body; the state keyword is the only
733
740
  // structural one logicprobe emits, everything else in that family is a state diagram too.
734
741
  if (/^\s*participant\b/m.test(text) || /->>\s*/.test(text)) return 'sequence'
742
+ // A use case diagram has no keyword header at all. The state parser below would read
743
+ // its arrows as transitions and return a plausible machine with no finding, so the
744
+ // family is refused by name. Its own model, and the tool that reads it, live in the
745
+ // dsh-model-everything plugin.
746
+ if (USECASE_SYNTAX.test(text)) {
747
+ throw new UmlError('this text is a use case diagram (`:Actor: --> (UseCase)`, `(UseCase) as (U)`), which this plugin does not model: it models state machines and flows, and reading it as one returns a machine the diagram never drew. The dsh-model-everything plugin renders, reads and reviews use case diagrams; keep this diagram as its own view.', 'UML_NOT_A_STATE_DIAGRAM')
748
+ }
735
749
  return 'state'
736
750
  }
737
751
  throw new UmlError('cannot tell which diagram kind this text declares')