dsh-logicprobe 1.2.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 +2 -2
- package/README.md +2 -2
- package/lib/types/uml.d.ts +293 -256
- package/lib/uml-tool.js +153 -145
- package/lib/uml.js +1812 -1748
- package/package.json +1 -1
- package/skills/logicprobe/SKILL.md +19 -0
- package/skills/logicprobe-concurrency/SKILL.md +1 -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/uml-tool.ts +11 -3
- package/src/uml.ts +94 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-logicprobe",
|
|
3
|
-
"version": "1.
|
|
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",
|
|
@@ -203,6 +203,25 @@ Turn the verdict into an answer, never into a summary of `ok`:
|
|
|
203
203
|
|
|
204
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.
|
|
205
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
|
+
|
|
206
225
|
### Slice Acceptance: Baseline Diffs
|
|
207
226
|
|
|
208
227
|
The acceptance criterion for a refactoring slice is usually "no **new** findings", not
|
|
@@ -42,3 +42,4 @@ The full routing table, including hard-real-time, hybrid-control and execution-c
|
|
|
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
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
|
-
##
|
|
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
|
-
|
|
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
|
|
package/src/uml-tool.ts
CHANGED
|
@@ -2,7 +2,7 @@ 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
6
|
import { renderReportText } from './report-view.js'
|
|
7
7
|
|
|
8
8
|
export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml'
|
|
@@ -22,7 +22,7 @@ export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml'
|
|
|
22
22
|
export const logicProbeUmlTool = defineTool({
|
|
23
23
|
name: LOGICPROBE_UML_TOOL_NAME,
|
|
24
24
|
description:
|
|
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. 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.',
|
|
26
26
|
parameters: {
|
|
27
27
|
action: {
|
|
28
28
|
type: 'string',
|
|
@@ -48,6 +48,11 @@ export const logicProbeUmlTool = defineTool({
|
|
|
48
48
|
enum: ['state', 'activity', 'sequence'],
|
|
49
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.',
|
|
50
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
|
+
},
|
|
51
56
|
roundTrip: {
|
|
52
57
|
type: 'boolean',
|
|
53
58
|
description: 'review only: render and re-parse the model to prove the diagram carries it. Default true.',
|
|
@@ -79,11 +84,12 @@ export const logicProbeUmlTool = defineTool({
|
|
|
79
84
|
try {
|
|
80
85
|
if (args.action === 'render') {
|
|
81
86
|
if (args.model === undefined) return errorResult('action=render needs `model` (a LogicModelV1 object)')
|
|
82
|
-
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)
|
|
83
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
|
|
84
89
|
}
|
|
85
90
|
if (args.action === 'parse') {
|
|
86
91
|
if (args.diagram === undefined) return errorResult('action=parse needs `diagram` (Mermaid or PlantUML text)')
|
|
92
|
+
checkUmlIntent(args.intent as UmlIntent | undefined, 'parse')
|
|
87
93
|
const result = parseUml(args.diagram, args.notation ?? 'auto')
|
|
88
94
|
// A diagram from another family still parses into *something*. `parseFindings`
|
|
89
95
|
// says so, and the verdict is what stops that something from being trusted:
|
|
@@ -109,6 +115,7 @@ export const logicProbeUmlTool = defineTool({
|
|
|
109
115
|
// R9: the label contract was documented in the library and in `explainLabels()`, but no
|
|
110
116
|
// tool exposed it, so it was reachable only by reading the source or the docs.
|
|
111
117
|
if (args.action === 'explain-labels') {
|
|
118
|
+
checkUmlIntent(args.intent as UmlIntent | undefined, 'labels')
|
|
112
119
|
const notation = (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation) as UmlNotation
|
|
113
120
|
return {
|
|
114
121
|
ok: true,
|
|
@@ -126,6 +133,7 @@ export const logicProbeUmlTool = defineTool({
|
|
|
126
133
|
diagramKind: (args.kind ?? 'state') as UmlDiagram,
|
|
127
134
|
roundTrip: args.roundTrip,
|
|
128
135
|
maxSteps: args.maxSteps,
|
|
136
|
+
intent: args.intent as UmlIntent | undefined,
|
|
129
137
|
})
|
|
130
138
|
return report as unknown as JsonValue
|
|
131
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
|