dsh-model-everything 0.1.0 → 0.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 +102 -81
- package/README.md +88 -76
- package/lib/automaton-tool.js +69 -0
- package/lib/automaton.js +327 -0
- package/lib/compose-tool.js +11 -2
- package/lib/concurrency-tool.js +6 -1
- package/lib/data-engine.js +1 -1
- package/lib/data-tool.js +18 -5
- package/lib/design-tool.js +21 -8
- package/lib/edit-tool.js +111 -0
- package/lib/editor.js +293 -0
- package/lib/engine.js +8 -1
- package/lib/export-tool.js +6 -1
- package/lib/index.js +4 -0
- package/lib/model-file.js +48 -0
- package/lib/presentation.js +81 -0
- package/lib/report-diff-tool.js +6 -1
- package/lib/report-view.js +92 -15
- package/lib/routing.js +85 -0
- package/lib/scan.js +547 -0
- package/lib/structure-tool.js +34 -1
- package/lib/structure.js +28 -3
- package/lib/tool.js +19 -4
- package/lib/types/automaton-tool.d.ts +2 -0
- package/lib/types/automaton.d.ts +57 -0
- package/lib/types/edit-tool.d.ts +2 -0
- package/lib/types/editor.d.ts +46 -0
- package/lib/types/engine.d.ts +1 -0
- package/lib/types/model-file.d.ts +7 -0
- package/lib/types/presentation.d.ts +59 -0
- package/lib/types/report-view.d.ts +8 -1
- package/lib/types/routing.d.ts +31 -0
- package/lib/types/scan.d.ts +37 -0
- package/lib/types/structure.d.ts +16 -0
- package/lib/uml-tool.js +16 -6
- package/package.json +8 -3
- package/skills/model-everything/SKILL.md +51 -79
- package/skills/model-everything/references/automata.md +102 -0
- package/skills/model-everything-interleaving/SKILL.zh-CN.md +60 -60
- package/skills/model-everything-report-reading/SKILL.md +137 -137
- package/src/automaton-tool.ts +73 -0
- package/src/automaton.ts +375 -0
- package/src/compose-tool.ts +13 -3
- package/src/concurrency-tool.ts +7 -1
- package/src/data-engine.ts +1 -1
- package/src/data-tool.ts +19 -5
- package/src/design-tool.ts +74 -60
- package/src/edit-tool.ts +119 -0
- package/src/editor.ts +297 -0
- package/src/engine.ts +8 -1
- package/src/export-tool.ts +87 -81
- package/src/index.ts +4 -0
- package/src/model-file.ts +49 -0
- package/src/presentation.ts +125 -0
- package/src/report-diff-tool.ts +7 -1
- package/src/report-view.ts +534 -451
- package/src/routing.ts +96 -0
- package/src/scan.ts +564 -0
- package/src/structure-tool.ts +33 -1
- package/src/structure.ts +37 -3
- package/src/tool.ts +19 -4
- package/src/uml-tool.ts +240 -230
package/src/structure-tool.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
|
2
2
|
import type { JsonValue } from './json-value.js'
|
|
3
3
|
import { REPORT_SCHEMAS } from './engine.js'
|
|
4
4
|
import { reviewStructure } from './structure.js'
|
|
5
|
+
import { scanDependencyGraph } from './scan.js'
|
|
5
6
|
import { reviewGranularity, type GranularityDiagram } from './granularity.js'
|
|
6
7
|
import { OptionShapeError, optionRefusal, requireArrayOption } from './option-shape.js'
|
|
7
8
|
import { renderReportText } from './report-view.js'
|
|
9
|
+
import { reportCard, reportResultView } from './presentation.js'
|
|
8
10
|
|
|
9
11
|
export const STRUCTURE_TOOL_NAME = 'uml_structure_verify'
|
|
10
12
|
|
|
@@ -19,7 +21,7 @@ export const STRUCTURE_TOOL_NAME = 'uml_structure_verify'
|
|
|
19
21
|
export const umlStructureTool = defineTool({
|
|
20
22
|
name: STRUCTURE_TOOL_NAME,
|
|
21
23
|
description:
|
|
22
|
-
'Audit a structure diagram as a dependency graph. Takes PlantUML component, package, class or deployment text, or a Mermaid class diagram. Reports UML020 isolated node, UML021 dangling reference, UML022 cycle (shortest path), UML023 disallowed edge and UML024 layer violation against a matrix, UML025 missing required edge, and UML026 when no node is declared. The matrix is {rules: [{id, source, allow?, deny?, require?}], layers: [{name, members}], default: "allow"|"deny"}. Ids and members accept globs (`*`, `?`). Deny beats allow, and layers run top-first, so downward edges pass and upward ones are UML024. With a matrix, every edge appears in `edgeVerdicts` with the rules it matched. Pass `diagrams` instead of `diagram` to check several levels at once: each child must refine its parent.
|
|
24
|
+
'Audit a structure diagram as a dependency graph. Takes PlantUML component, package, class or deployment text, or a Mermaid class diagram - or `repoPath`, a source tree, in which case it builds the graph from the import constructs the source itself uses and no drawing is needed. Reports UML020 isolated node, UML021 dangling reference, UML022 cycle (shortest path), UML023 disallowed edge and UML024 layer violation against a matrix, UML025 missing required edge, and UML026 when no node is declared. The matrix is {rules: [{id, source, allow?, deny?, require?}], layers: [{name, members}], default: "allow"|"deny"}. Ids and members accept globs (`*`, `?`). Deny beats allow, and layers run top-first, so downward edges pass and upward ones are UML024. With a matrix, every edge appears in `edgeVerdicts` with the rules it matched. Pass `diagrams` instead of `diagram` to check several levels at once: each child must refine its parent. It answers **what the graph is**: which edges exist, which node has no edge, which cycle, and whether an edge breaks a matrix you supplied. It does not judge whether the design meant to allow those edges - `model_design_review` does that against the rules the design declares for itself. When the question is whether an import exists from one layer to another, this answers it, from the code. Read `verdict`, not `ok`. For state or activity diagrams use `uml` and `model_verify`.',
|
|
23
25
|
parameters: {
|
|
24
26
|
diagram: {
|
|
25
27
|
type: 'string',
|
|
@@ -34,6 +36,10 @@ export const umlStructureTool = defineTool({
|
|
|
34
36
|
enum: ['auto', 'plantuml', 'mermaid'],
|
|
35
37
|
description: 'Diagram language. Default auto (detected from the text).',
|
|
36
38
|
},
|
|
39
|
+
repoPath: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Directory or single source file to scan instead of drawing the diagram yourself. Reads the import construct of C/C++, Python, TypeScript/JavaScript, Go, Rust, Java/Kotlin/Scala, C#, PHP, Ruby, Lua, Zig, Dart and Elixir, building a file graph for file languages and a package graph for package languages (Go, Java, C#). The graph is reviewed by the same checks; `diagram` and `diagrams` are then ignored.',
|
|
42
|
+
},
|
|
37
43
|
matrix: {
|
|
38
44
|
type: 'json',
|
|
39
45
|
description: 'Optional dependency matrix: {rules: [{id, source, allow?, deny?, require?}], layers: [{name, members}], default: "allow"|"deny"}. Every edge is judged against it and the matched rule ids are reported.',
|
|
@@ -52,7 +58,12 @@ export const umlStructureTool = defineTool({
|
|
|
52
58
|
render(args, value) {
|
|
53
59
|
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
54
60
|
},
|
|
61
|
+
// The card a UI shows, separated from the text the model reads.
|
|
62
|
+
presentationMeta(_args, value) {
|
|
63
|
+
return reportCard(value, STRUCTURE_TOOL_NAME) as unknown as JsonValue
|
|
64
|
+
},
|
|
55
65
|
},
|
|
66
|
+
presentResult: reportResultView(STRUCTURE_TOOL_NAME),
|
|
56
67
|
timeoutMs: 10_000,
|
|
57
68
|
isConcurrencySafe: () => true,
|
|
58
69
|
async execute(args) {
|
|
@@ -81,6 +92,27 @@ export const umlStructureTool = defineTool({
|
|
|
81
92
|
return optionRefusal(error, 'STRUCTURE_OPTION_INVALID') as unknown as JsonValue
|
|
82
93
|
}
|
|
83
94
|
}
|
|
95
|
+
if (args.repoPath !== undefined) {
|
|
96
|
+
try {
|
|
97
|
+
const scanned = scanDependencyGraph(args.repoPath)
|
|
98
|
+
// A source module is a declared node that depends on others: not a container, and it has a
|
|
99
|
+
// line only when it came from a drawing, so the field stays absent.
|
|
100
|
+
const graph = {
|
|
101
|
+
notation: 'plantuml' as const,
|
|
102
|
+
nodes: scanned.nodes.map((id) => ({ id, label: id, kind: 'component' as const, container: false, declared: true })),
|
|
103
|
+
edges: scanned.edges.map((edge) => ({ from: edge.from, to: edge.to, line: 0 })),
|
|
104
|
+
warnings: [],
|
|
105
|
+
}
|
|
106
|
+
return reviewStructure({
|
|
107
|
+
diagram: '',
|
|
108
|
+
graph,
|
|
109
|
+
scan: { files: scanned.filesRead, unresolved: scanned.unresolved.length, packages: scanned.packages.length, languages: scanned.languages.map((entry) => entry.language) },
|
|
110
|
+
...(args.matrix === undefined ? {} : { matrix: args.matrix }),
|
|
111
|
+
}) as unknown as JsonValue
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return optionRefusal(error, 'STRUCTURE_OPTION_INVALID') as unknown as JsonValue
|
|
114
|
+
}
|
|
115
|
+
}
|
|
84
116
|
if (args.diagram === undefined) {
|
|
85
117
|
return { ok: false, ran: false, verdict: 'fail', verdictReason: 'the tool did not run: pass `diagram` (one structure diagram) or `diagrams` (several levels with parents)', schema: REPORT_SCHEMAS.structure, errorCode: 'UML_INPUT', error: 'structure review needs `diagram` or `diagrams`' } as unknown as JsonValue
|
|
86
118
|
}
|
package/src/structure.ts
CHANGED
|
@@ -107,6 +107,10 @@ export interface StructureSummary {
|
|
|
107
107
|
errors: number
|
|
108
108
|
warnings: number
|
|
109
109
|
rules: number
|
|
110
|
+
/** The most depended-on nodes, by incoming edges. What a reader asks first and counts worst. */
|
|
111
|
+
hubs: { id: string; dependents: number }[]
|
|
112
|
+
/** Modules nothing depends on (no outgoing edges), capped for the report. */
|
|
113
|
+
leaves: string[]
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
export interface StructureReport {
|
|
@@ -503,6 +507,10 @@ export function shortestCycle(nodes: string[], edges: StructureEdge[]): string[]
|
|
|
503
507
|
export interface StructureReviewOptions {
|
|
504
508
|
/** Diagram text to review. */
|
|
505
509
|
diagram: string
|
|
510
|
+
/** A graph built from source, instead of parsed from a diagram. `diagram` is then ignored. */
|
|
511
|
+
graph?: StructureGraph
|
|
512
|
+
/** How the graph was produced, when it came from a scan rather than a drawing. */
|
|
513
|
+
scan?: { files: number; unresolved: number; packages: number; languages: string[] }
|
|
506
514
|
notation?: 'auto' | StructureNotation
|
|
507
515
|
/** Dependency matrix: allowed/denied edges, layers, required edges. */
|
|
508
516
|
matrix?: unknown
|
|
@@ -531,8 +539,9 @@ function requireDiagramText(value: unknown): string {
|
|
|
531
539
|
export function reviewStructure(options: StructureReviewOptions): StructureReport {
|
|
532
540
|
// Not `if (options.diagram !== undefined)`: a missing `diagram` reached the parser as `undefined`
|
|
533
541
|
// and threw a read of `split` on it - the same defect, one guard narrower.
|
|
534
|
-
|
|
535
|
-
|
|
542
|
+
// A graph built from source arrives already parsed; a caller that passes one has no diagram text to
|
|
543
|
+
// require, so the text guard belongs to the other branch.
|
|
544
|
+
const graph = options.graph ?? parseStructure(requireDiagramText(options.diagram), options.notation ?? 'auto')
|
|
536
545
|
const warnings = [...graph.warnings]
|
|
537
546
|
const findings: Finding[] = []
|
|
538
547
|
const nodes = graph.nodes
|
|
@@ -730,6 +739,26 @@ export function reviewStructure(options: StructureReviewOptions): StructureRepor
|
|
|
730
739
|
})
|
|
731
740
|
}
|
|
732
741
|
|
|
742
|
+
// Degree is what a reader asks first and counts worst: which module everything depends on, and which
|
|
743
|
+
// depends on nothing. Counting it here means the answer is one line in the report rather than a scan of
|
|
744
|
+
// the graph by hand, which is where a correct graph still produced a wrong answer.
|
|
745
|
+
const inDegree = new Map<string, number>()
|
|
746
|
+
const outDegree = new Map<string, number>()
|
|
747
|
+
for (const edge of graph.edges) {
|
|
748
|
+
inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1)
|
|
749
|
+
outDegree.set(edge.from, (outDegree.get(edge.from) ?? 0) + 1)
|
|
750
|
+
}
|
|
751
|
+
const hubs = [...inDegree.entries()]
|
|
752
|
+
.filter(([, count]) => count > 0)
|
|
753
|
+
.sort((a, b) => (b[1] === a[1] ? a[0].localeCompare(b[0]) : b[1] - a[1]))
|
|
754
|
+
.slice(0, 5)
|
|
755
|
+
.map(([id, dependents]) => ({ id, dependents }))
|
|
756
|
+
const leaves = nodes
|
|
757
|
+
.filter((node) => !node.container && (outDegree.get(node.id) ?? 0) === 0)
|
|
758
|
+
.map((node) => node.id)
|
|
759
|
+
.sort()
|
|
760
|
+
.slice(0, 10)
|
|
761
|
+
|
|
733
762
|
const errors = findings.filter((finding) => finding.severity === 'error').length
|
|
734
763
|
const warningCount = findings.filter((finding) => finding.severity === 'warning').length
|
|
735
764
|
const summary: StructureSummary = {
|
|
@@ -745,6 +774,8 @@ export function reviewStructure(options: StructureReviewOptions): StructureRepor
|
|
|
745
774
|
errors,
|
|
746
775
|
warnings: warningCount,
|
|
747
776
|
rules: rules.length,
|
|
777
|
+
hubs,
|
|
778
|
+
leaves,
|
|
748
779
|
}
|
|
749
780
|
|
|
750
781
|
const nextSteps: string[] = []
|
|
@@ -761,10 +792,13 @@ export function reviewStructure(options: StructureReviewOptions): StructureRepor
|
|
|
761
792
|
notation: graph.notation,
|
|
762
793
|
graph,
|
|
763
794
|
...(options.matrix === undefined ? {} : { edgeVerdicts }),
|
|
795
|
+
...(options.scan === undefined ? {} : { scan: options.scan }),
|
|
764
796
|
findings,
|
|
765
797
|
summary,
|
|
766
798
|
hashes: {
|
|
767
|
-
diagram
|
|
799
|
+
// A graph built from source has no diagram text to hash; hashing the text would hash the empty
|
|
800
|
+
// string, and every scan would then carry the same input hash.
|
|
801
|
+
diagram: createHash('sha256').update(options.graph === undefined ? options.diagram : canonicalJson({ nodes: options.graph.nodes.map((node) => node.id), edges: options.graph.edges.map((edge) => ({ from: edge.from, to: edge.to })) })).digest('hex'),
|
|
768
802
|
...(options.matrix === undefined ? {} : { matrix: createHash('sha256').update(canonicalJson(options.matrix)).digest('hex') }),
|
|
769
803
|
},
|
|
770
804
|
warnings,
|
package/src/tool.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
|
2
2
|
import type { JsonValue } from './json-value.js'
|
|
3
3
|
import { runVerification } from './engine.js'
|
|
4
4
|
import { renderReportText } from './report-view.js'
|
|
5
|
+
import { reportCard, reportResultView } from './presentation.js'
|
|
5
6
|
import { OptionShapeError, optionRefusal, requireObjectOption } from './option-shape.js'
|
|
7
|
+
import { oneOf } from './model-file.js'
|
|
6
8
|
|
|
7
9
|
export const MODEL_VERIFY_TOOL_NAME = 'model_verify'
|
|
8
10
|
|
|
@@ -22,8 +24,11 @@ export const modelVerifyTool = defineTool({
|
|
|
22
24
|
parameters: {
|
|
23
25
|
model: {
|
|
24
26
|
type: 'json',
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
description: 'LogicModelV1 state machine model to verify. Pass this, or modelPath, not both.',
|
|
28
|
+
},
|
|
29
|
+
modelPath: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Path to a JSON file holding the model, instead of inlining it. Formatting does not matter: whitespace, a BOM and pretty printing all parse. Relative paths resolve against the process working directory, which is the session workspace.',
|
|
27
32
|
},
|
|
28
33
|
maxStates: {
|
|
29
34
|
type: 'integer',
|
|
@@ -41,6 +46,10 @@ export const modelVerifyTool = defineTool({
|
|
|
41
46
|
type: 'json',
|
|
42
47
|
description: 'Optional object mapping BEFORE state ids to AFTER state ids. Omit for identity mapping (same state names).',
|
|
43
48
|
},
|
|
49
|
+
check: {
|
|
50
|
+
type: 'string',
|
|
51
|
+
description: 'Print one check in full when the digest truncated it. Pass the check id the CHECKS line lists (for example A9); the per-check budget no longer applies, and an id this report does not carry is answered with the ids that it does.',
|
|
52
|
+
},
|
|
44
53
|
reportView: {
|
|
45
54
|
type: 'string',
|
|
46
55
|
enum: ['digest', 'full'],
|
|
@@ -53,9 +62,14 @@ export const modelVerifyTool = defineTool({
|
|
|
53
62
|
description: 'this plugin verification report with summary and per-check findings.',
|
|
54
63
|
},
|
|
55
64
|
render(args, value) {
|
|
56
|
-
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
65
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full', check: args.check }) }]
|
|
66
|
+
},
|
|
67
|
+
// The card a UI shows, separated from the text the model reads.
|
|
68
|
+
presentationMeta(_args, value) {
|
|
69
|
+
return reportCard(value, MODEL_VERIFY_TOOL_NAME) as unknown as JsonValue
|
|
57
70
|
},
|
|
58
71
|
},
|
|
72
|
+
presentResult: reportResultView(MODEL_VERIFY_TOOL_NAME),
|
|
59
73
|
timeoutMs: 10_000,
|
|
60
74
|
isConcurrencySafe: () => true,
|
|
61
75
|
async execute(args) {
|
|
@@ -81,7 +95,8 @@ export const modelVerifyTool = defineTool({
|
|
|
81
95
|
)
|
|
82
96
|
}
|
|
83
97
|
}
|
|
84
|
-
|
|
98
|
+
const model = oneOf(args.model, args.modelPath, 'model', 'modelPath', 'VERIFY_OPTION_INVALID')
|
|
99
|
+
return runVerification(model, {
|
|
85
100
|
maxStates: args.maxStates,
|
|
86
101
|
maxPermutationEvents: args.maxPermutationEvents,
|
|
87
102
|
beforeModel: args.beforeModel,
|