dsh-logicprobe 0.8.1 → 1.0.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.
Files changed (65) hide show
  1. package/README.en-US.md +79 -14
  2. package/README.md +79 -14
  3. package/lib/baseline.js +142 -0
  4. package/lib/client.js +56 -1
  5. package/lib/compose-tool.js +1 -1
  6. package/lib/concurrency-tool.js +1 -1
  7. package/lib/concurrency.js +14 -0
  8. package/lib/data-engine.js +42 -8
  9. package/lib/engine.js +609 -118
  10. package/lib/export-tool.js +27 -2
  11. package/lib/granularity.js +237 -0
  12. package/lib/index.js +63 -32
  13. package/lib/report-diff-tool.js +39 -0
  14. package/lib/structure-tool.js +64 -0
  15. package/lib/structure.js +648 -0
  16. package/lib/tool.js +3 -3
  17. package/lib/types/baseline.d.ts +69 -0
  18. package/lib/types/concurrency.d.ts +10 -0
  19. package/lib/types/data-engine.d.ts +13 -1
  20. package/lib/types/engine.d.ts +139 -2
  21. package/lib/types/granularity.d.ts +106 -0
  22. package/lib/types/index.d.ts +9 -8
  23. package/lib/types/report-diff-tool.d.ts +8 -0
  24. package/lib/types/structure-tool.d.ts +10 -0
  25. package/lib/types/structure.d.ts +156 -0
  26. package/lib/types/tool.d.ts +2 -2
  27. package/lib/types/uml.d.ts +89 -0
  28. package/lib/uml-tool.js +36 -7
  29. package/lib/uml.js +217 -23
  30. package/locale/en.json +6 -0
  31. package/locale/zh.json +6 -0
  32. package/package.json +16 -14
  33. package/skills/logicprobe/SKILL.md +97 -58
  34. package/skills/logicprobe/references/dsh-model-schema.md +87 -15
  35. package/skills/logicprobe/references/gap-routing-guide.md +37 -36
  36. package/skills/logicprobe/references/hash-spec.md +128 -0
  37. package/skills/logicprobe/references/logic-verification-guide.md +29 -248
  38. package/skills/logicprobe-concurrency/SKILL.md +43 -0
  39. package/skills/{logicprobe → logicprobe-concurrency}/references/concurrency-risk-guide.md +1 -1
  40. package/skills/logicprobe-datamodel/SKILL.md +5 -4
  41. package/skills/logicprobe-datamodel/references/data-model-guide.md +1 -1
  42. package/skills/logicprobe-datamodel/references/data-model-schema.md +1 -1
  43. package/skills/logicprobe-structure/SKILL.md +95 -0
  44. package/skills/logicprobe-structure/references/structure-review-guide.md +177 -0
  45. package/skills/logicprobe-uml/SKILL.md +55 -0
  46. package/skills/{logicprobe → logicprobe-uml}/references/uml-modeling-guide.md +120 -8
  47. package/src/baseline.ts +180 -0
  48. package/src/client.js +56 -1
  49. package/src/compose-tool.ts +47 -47
  50. package/src/concurrency-tool.ts +2 -2
  51. package/src/concurrency.ts +23 -0
  52. package/src/data-engine.ts +51 -9
  53. package/src/engine.ts +656 -101
  54. package/src/export-tool.ts +73 -48
  55. package/src/granularity.ts +312 -0
  56. package/src/index.ts +63 -32
  57. package/src/report-diff-tool.ts +43 -0
  58. package/src/structure-tool.ts +68 -0
  59. package/src/structure.ts +724 -0
  60. package/src/tool.ts +4 -4
  61. package/src/uml-tool.ts +38 -8
  62. package/src/uml.ts +275 -24
  63. package/skills/logicprobe/references/logicprobe-engine.py +0 -4780
  64. package/skills/logicprobe/references/verification-harness.py +0 -959
  65. package/skills/logicprobe-datamodel/references/data-model-harness.py +0 -528
@@ -30,6 +30,7 @@
30
30
  *
31
31
  * @module logicprobe-uml
32
32
  */
33
+ import { type HashSpec, type NarrativeCoverage, type ReportSchema, type Verdict } from './engine.js';
33
34
  import type { GuardNode, LogicModelV1, UpdateSpec } from './engine.js';
34
35
  export type UmlNotation = 'mermaid' | 'plantuml';
35
36
  export type UmlDiagram = 'state' | 'activity' | 'sequence';
@@ -52,8 +53,29 @@ export interface UmlParseResult {
52
53
  * symbol, which the review reports.
53
54
  */
54
55
  labels: Record<string, string>;
56
+ /**
57
+ * Declarations found in the text that LogicModelV1 cannot represent. A non-empty
58
+ * list means the parsed model is NOT this diagram: the text belongs to another
59
+ * diagram family (component, package, class, deployment, …), so whatever came
60
+ * out is a by-product of reading keywords the parser does not own.
61
+ */
62
+ discardedConstructs: DiscardedConstruct[];
63
+ /**
64
+ * Arrows whose endpoints were declared by a discarded construct. They survive
65
+ * parsing as state transitions, which is exactly why they are counted out loud.
66
+ */
67
+ discardedEdges: number;
55
68
  warnings: string[];
56
69
  }
70
+ /** One declaration the parser could not represent, with the source line that carried it. */
71
+ export interface DiscardedConstruct {
72
+ /** The construct keyword, lower-cased (`component`, `package`, `classDiagram`). */
73
+ construct: string;
74
+ /** 1-based line number in the diagram source. */
75
+ line: number;
76
+ /** The source line, trimmed. */
77
+ text: string;
78
+ }
57
79
  export interface UmlFinding {
58
80
  code: string;
59
81
  severity: 'error' | 'warning' | 'info';
@@ -66,18 +88,28 @@ export interface UmlFinding {
66
88
  to: string;
67
89
  }>;
68
90
  detail?: string;
91
+ /** Machine-readable context for the finding (ids, coverage, rule matches), same shape as the engine's findings. */
92
+ evidence?: Record<string, unknown>;
69
93
  }
70
94
  export interface UmlRoundTripReport {
71
95
  notation: UmlNotation;
72
96
  diagram: UmlDiagram;
73
97
  ok: boolean;
98
+ /** Which published hash specification the two hashes follow (see references/hash-spec.md). */
99
+ hashSpec: HashSpec;
74
100
  modelHash: string;
75
101
  parsedHash: string;
76
102
  diffs: string[];
77
103
  warnings: string[];
78
104
  }
79
105
  export interface UmlReviewReport {
106
+ /** The review ran and produced this report. It does NOT mean the modelling passed — read `verdict`. */
80
107
  ok: boolean;
108
+ ran: boolean;
109
+ verdict: Verdict;
110
+ verdictReason: string;
111
+ /** The versioned report contract this result follows. */
112
+ schema: ReportSchema;
81
113
  source: 'model' | 'diagram' | 'model+diagram';
82
114
  summary: {
83
115
  errors: number;
@@ -98,10 +130,30 @@ export interface UmlReviewReport {
98
130
  model?: LogicModelV1;
99
131
  /** Diagram rendered from the model, when only a model was given. */
100
132
  primary?: string;
133
+ /** Paths of the `_`-prefixed metadata keys found in the supplied model, when it carried any. */
134
+ metadataKeys?: string[];
135
+ /** How much of the model the narrative documents; absent when there is no narrative. */
136
+ narrativeCoverage?: NarrativeCoverage;
137
+ /** Hashes of what was reviewed, in one place. */
138
+ hashes: {
139
+ hashSpec: HashSpec;
140
+ modelHash?: string;
141
+ parsedHash?: string;
142
+ };
143
+ /** Declarations the parser could not represent; a non-empty list fails the review. */
144
+ discardedConstructs?: DiscardedConstruct[];
145
+ /** Arrows whose endpoints came from a discarded construct. */
146
+ discardedEdges?: number;
101
147
  warnings: string[];
102
148
  nextSteps: string[];
103
149
  }
150
+ /**
151
+ * A UML front-end refusal. `code` lets the tool report *why* it refused instead of
152
+ * collapsing every refusal into a generic input error.
153
+ */
104
154
  export declare class UmlError extends Error {
155
+ readonly code: string;
156
+ constructor(message: string, code?: string);
105
157
  }
106
158
  /** Canonical guard text. Rendering wraps every composite node in parentheses, and the parser flattens same-operator chains, so render∘parse is the identity. */
107
159
  export declare function guardText(node: GuardNode): string;
@@ -124,6 +176,43 @@ export declare function renderUml(input: unknown, notation?: UmlNotation, diagra
124
176
  export declare function parseGuardText(text: string): GuardNode;
125
177
  /** Parse a UML action clause such as `retry := retry + 1, armed := true`. */
126
178
  export declare function parseUpdatesText(text: string, warnings: string[]): UpdateSpec[];
179
+ /**
180
+ * Findings a parse result carries on its own. A diagram from another family parses
181
+ * into something; without this finding that something is presented as a model of the
182
+ * file, which is a false guarantee of exactly the kind this plugin exists to prevent.
183
+ */
184
+ export declare function parseFindings(parsed: UmlParseResult): UmlFinding[];
185
+ /** One accepted way of writing a state's meaning into a diagram label. */
186
+ export interface LabelForm {
187
+ form: string;
188
+ example: string;
189
+ note: string;
190
+ }
191
+ export interface LabelExplanation {
192
+ notation: UmlNotation;
193
+ /** The comment prefix a `logicprobe:` directive uses in this notation. */
194
+ directivePrefix: string;
195
+ directiveLines: Array<{
196
+ example: string;
197
+ meaning: string;
198
+ }>;
199
+ /** Accepted label spellings, in the order `documentedMeaning` accepts them. */
200
+ acceptedLabelForms: LabelForm[];
201
+ /** What the renderer itself writes, so a hand-edited diagram can match it. */
202
+ renderedForm: string;
203
+ ignoredLines: Array<{
204
+ pattern: string;
205
+ behaviour: string;
206
+ }>;
207
+ rules: string[];
208
+ }
209
+ /**
210
+ * What the front end expects of labels and comments, so "why does my hand-drawn diagram
211
+ * report label drift or ignored lines?" has a printed answer instead of a guess.
212
+ * `tests/uml/run.mjs` feeds every claim here back through the parser, so this text cannot
213
+ * drift away from the behaviour it describes.
214
+ */
215
+ export declare function explainLabels(notation?: UmlNotation): LabelExplanation;
127
216
  /**
128
217
  * Parse a Mermaid or PlantUML diagram back into a LogicModelV1.
129
218
  *
package/lib/uml-tool.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { defineTool } from '@deepseek-ai/dsh-tools';
2
- import { renderUml, parseUml, reviewUml, UmlError } from './uml.js';
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';
3
5
  export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml';
4
6
  /**
5
7
  * DSH tool wrapping the UML front end: model a code flow as a UML diagram
@@ -15,7 +17,7 @@ export const LOGICPROBE_UML_TOOL_NAME = 'logicprobe_uml';
15
17
  */
16
18
  export const logicProbeUmlTool = defineTool({
17
19
  name: LOGICPROBE_UML_TOOL_NAME,
18
- description: 'Model a code flow as UML, and review the modelling. action="render" turns a LogicModelV1 (schemaVersion=1) into diagram source: notation mermaid (state | activity flowchart | sequence) or plantuml (state | sequence). action="parse" reads Mermaid/PlantUML state or activity text back into a LogicModelV1 (plus the display labels it found), so a hand-drawn diagram can be verified with logicprobe_verify; a sequence diagram is refused because a trace cannot reconstruct a machine. action="review" audits the modelling: structural defects (UML002 unreachable state, UML003 dead end, UML004 ambiguous branch, UML005 overlapping guard, UML006 inexhaustive branch, UML007 unused event, UML008 self-loop with no exit, UML009 duplicate transition), documentation gaps (UML010 unused variable, UML011 unbounded variable, UML012 no terminal, UML013 no narrative, UML014 undocumented state, UML015 label drift), and the fidelity check — the diagram is rendered and re-parsed and any structural difference is reported as UML017 round-trip mismatch. Give review a model (checks it, renders and re-parses it), a diagram (parses and reviews that), or both (checks the diagram against the model). Rendering never invents structure and never silently drops a construct the notation cannot express: those become warnings. Review does NOT replace logicprobe_verify — it covers the modelling, not behaviour; findings name the engine check to run next.',
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.',
19
21
  parameters: {
20
22
  action: {
21
23
  type: 'string',
@@ -76,13 +78,32 @@ export const logicProbeUmlTool = defineTool({
76
78
  if (args.model === undefined)
77
79
  return errorResult('action=render needs `model` (a LogicModelV1 object)');
78
80
  const result = renderUml(args.model, (args.notation === undefined || args.notation === 'auto' ? 'mermaid' : args.notation), (args.kind ?? 'state'), args.maxSteps);
79
- return { ok: true, action: 'render', notation: result.notation, kind: result.diagram, diagram: result.primary, warnings: result.warnings };
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 };
80
82
  }
81
83
  if (args.action === 'parse') {
82
84
  if (args.diagram === undefined)
83
85
  return errorResult('action=parse needs `diagram` (Mermaid or PlantUML text)');
84
86
  const result = parseUml(args.diagram, args.notation ?? 'auto');
85
- return { ok: true, action: 'parse', notation: result.notation, kind: result.diagram, model: result.model, labels: result.labels, warnings: result.warnings };
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
+ };
86
107
  }
87
108
  const report = reviewUml({
88
109
  model: args.model,
@@ -95,10 +116,18 @@ export const logicProbeUmlTool = defineTool({
95
116
  return report;
96
117
  }
97
118
  catch (error) {
98
- return errorResult(error instanceof Error ? error.message : String(error), error instanceof UmlError);
119
+ const message = error instanceof Error ? error.message : String(error);
120
+ return errorResult(message, error instanceof UmlError ? error.code : undefined);
99
121
  }
100
122
  },
101
123
  });
102
- function errorResult(message, isUmlError = false) {
103
- return { ok: false, ...(isUmlError ? { errorCode: 'UML_INPUT' } : {}), error: message };
124
+ /** The parse verdict counts parser notes as findings: an ignored line is information the model does not carry. */
125
+ function parseVerdict(result, findings) {
126
+ const errors = findings.filter((finding) => finding.severity === 'error').length;
127
+ const warnings = findings.filter((finding) => finding.severity === 'warning').length + result.warnings.length;
128
+ const firstCode = findings.find((finding) => finding.severity === 'error')?.code ?? findings.find((finding) => finding.severity === 'warning')?.code;
129
+ return verdictOf(errors, warnings, firstCode);
130
+ }
131
+ function errorResult(message, errorCode = 'UML_INPUT') {
132
+ return { ok: false, ran: false, ...refusalVerdict(message), errorCode, error: message };
104
133
  }