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.
Files changed (62) hide show
  1. package/README.en-US.md +102 -81
  2. package/README.md +88 -76
  3. package/lib/automaton-tool.js +69 -0
  4. package/lib/automaton.js +327 -0
  5. package/lib/compose-tool.js +11 -2
  6. package/lib/concurrency-tool.js +6 -1
  7. package/lib/data-engine.js +1 -1
  8. package/lib/data-tool.js +18 -5
  9. package/lib/design-tool.js +21 -8
  10. package/lib/edit-tool.js +111 -0
  11. package/lib/editor.js +293 -0
  12. package/lib/engine.js +8 -1
  13. package/lib/export-tool.js +6 -1
  14. package/lib/index.js +4 -0
  15. package/lib/model-file.js +48 -0
  16. package/lib/presentation.js +81 -0
  17. package/lib/report-diff-tool.js +6 -1
  18. package/lib/report-view.js +92 -15
  19. package/lib/routing.js +85 -0
  20. package/lib/scan.js +547 -0
  21. package/lib/structure-tool.js +34 -1
  22. package/lib/structure.js +28 -3
  23. package/lib/tool.js +19 -4
  24. package/lib/types/automaton-tool.d.ts +2 -0
  25. package/lib/types/automaton.d.ts +57 -0
  26. package/lib/types/edit-tool.d.ts +2 -0
  27. package/lib/types/editor.d.ts +46 -0
  28. package/lib/types/engine.d.ts +1 -0
  29. package/lib/types/model-file.d.ts +7 -0
  30. package/lib/types/presentation.d.ts +59 -0
  31. package/lib/types/report-view.d.ts +8 -1
  32. package/lib/types/routing.d.ts +31 -0
  33. package/lib/types/scan.d.ts +37 -0
  34. package/lib/types/structure.d.ts +16 -0
  35. package/lib/uml-tool.js +16 -6
  36. package/package.json +8 -3
  37. package/skills/model-everything/SKILL.md +51 -79
  38. package/skills/model-everything/references/automata.md +102 -0
  39. package/skills/model-everything-interleaving/SKILL.zh-CN.md +60 -60
  40. package/skills/model-everything-report-reading/SKILL.md +137 -137
  41. package/src/automaton-tool.ts +73 -0
  42. package/src/automaton.ts +375 -0
  43. package/src/compose-tool.ts +13 -3
  44. package/src/concurrency-tool.ts +7 -1
  45. package/src/data-engine.ts +1 -1
  46. package/src/data-tool.ts +19 -5
  47. package/src/design-tool.ts +74 -60
  48. package/src/edit-tool.ts +119 -0
  49. package/src/editor.ts +297 -0
  50. package/src/engine.ts +8 -1
  51. package/src/export-tool.ts +87 -81
  52. package/src/index.ts +4 -0
  53. package/src/model-file.ts +49 -0
  54. package/src/presentation.ts +125 -0
  55. package/src/report-diff-tool.ts +7 -1
  56. package/src/report-view.ts +534 -451
  57. package/src/routing.ts +96 -0
  58. package/src/scan.ts +564 -0
  59. package/src/structure-tool.ts +33 -1
  60. package/src/structure.ts +37 -3
  61. package/src/tool.ts +19 -4
  62. package/src/uml-tool.ts +240 -230
package/lib/structure.js CHANGED
@@ -444,8 +444,9 @@ function requireDiagramText(value) {
444
444
  export function reviewStructure(options) {
445
445
  // Not `if (options.diagram !== undefined)`: a missing `diagram` reached the parser as `undefined`
446
446
  // and threw a read of `split` on it - the same defect, one guard narrower.
447
- requireDiagramText(options.diagram);
448
- const graph = parseStructure(options.diagram, options.notation ?? 'auto');
447
+ // A graph built from source arrives already parsed; a caller that passes one has no diagram text to
448
+ // require, so the text guard belongs to the other branch.
449
+ const graph = options.graph ?? parseStructure(requireDiagramText(options.diagram), options.notation ?? 'auto');
449
450
  const warnings = [...graph.warnings];
450
451
  const findings = [];
451
452
  const nodes = graph.nodes;
@@ -657,6 +658,25 @@ export function reviewStructure(options) {
657
658
  evidence: { missing },
658
659
  });
659
660
  }
661
+ // Degree is what a reader asks first and counts worst: which module everything depends on, and which
662
+ // depends on nothing. Counting it here means the answer is one line in the report rather than a scan of
663
+ // the graph by hand, which is where a correct graph still produced a wrong answer.
664
+ const inDegree = new Map();
665
+ const outDegree = new Map();
666
+ for (const edge of graph.edges) {
667
+ inDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);
668
+ outDegree.set(edge.from, (outDegree.get(edge.from) ?? 0) + 1);
669
+ }
670
+ const hubs = [...inDegree.entries()]
671
+ .filter(([, count]) => count > 0)
672
+ .sort((a, b) => (b[1] === a[1] ? a[0].localeCompare(b[0]) : b[1] - a[1]))
673
+ .slice(0, 5)
674
+ .map(([id, dependents]) => ({ id, dependents }));
675
+ const leaves = nodes
676
+ .filter((node) => !node.container && (outDegree.get(node.id) ?? 0) === 0)
677
+ .map((node) => node.id)
678
+ .sort()
679
+ .slice(0, 10);
660
680
  const errors = findings.filter((finding) => finding.severity === 'error').length;
661
681
  const warningCount = findings.filter((finding) => finding.severity === 'warning').length;
662
682
  const summary = {
@@ -672,6 +692,8 @@ export function reviewStructure(options) {
672
692
  errors,
673
693
  warnings: warningCount,
674
694
  rules: rules.length,
695
+ hubs,
696
+ leaves,
675
697
  };
676
698
  const nextSteps = [];
677
699
  if (errors > 0)
@@ -689,10 +711,13 @@ export function reviewStructure(options) {
689
711
  notation: graph.notation,
690
712
  graph,
691
713
  ...(options.matrix === undefined ? {} : { edgeVerdicts }),
714
+ ...(options.scan === undefined ? {} : { scan: options.scan }),
692
715
  findings,
693
716
  summary,
694
717
  hashes: {
695
- diagram: createHash('sha256').update(options.diagram).digest('hex'),
718
+ // A graph built from source has no diagram text to hash; hashing the text would hash the empty
719
+ // string, and every scan would then carry the same input hash.
720
+ 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'),
696
721
  ...(options.matrix === undefined ? {} : { matrix: createHash('sha256').update(canonicalJson(options.matrix)).digest('hex') }),
697
722
  },
698
723
  warnings,
package/lib/tool.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { defineTool } from '@deepseek-ai/dsh-tools';
2
2
  import { runVerification } from './engine.js';
3
3
  import { renderReportText } from './report-view.js';
4
+ import { reportCard, reportResultView } from './presentation.js';
4
5
  import { OptionShapeError, optionRefusal, requireObjectOption } from './option-shape.js';
6
+ import { oneOf } from './model-file.js';
5
7
  export const MODEL_VERIFY_TOOL_NAME = 'model_verify';
6
8
  /**
7
9
  * Model-visible DSH tool wrapping the bundled TypeScript verification engine.
@@ -18,8 +20,11 @@ export const modelVerifyTool = defineTool({
18
20
  parameters: {
19
21
  model: {
20
22
  type: 'json',
21
- required: true,
22
- description: 'LogicModelV1 state machine model to verify.',
23
+ description: 'LogicModelV1 state machine model to verify. Pass this, or modelPath, not both.',
24
+ },
25
+ modelPath: {
26
+ type: 'string',
27
+ 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.',
23
28
  },
24
29
  maxStates: {
25
30
  type: 'integer',
@@ -37,6 +42,10 @@ export const modelVerifyTool = defineTool({
37
42
  type: 'json',
38
43
  description: 'Optional object mapping BEFORE state ids to AFTER state ids. Omit for identity mapping (same state names).',
39
44
  },
45
+ check: {
46
+ type: 'string',
47
+ 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.',
48
+ },
40
49
  reportView: {
41
50
  type: 'string',
42
51
  enum: ['digest', 'full'],
@@ -49,9 +58,14 @@ export const modelVerifyTool = defineTool({
49
58
  description: 'this plugin verification report with summary and per-check findings.',
50
59
  },
51
60
  render(args, value) {
52
- return [{ type: 'text', text: renderReportText(value, { full: args.reportView === 'full' }) }];
61
+ return [{ type: 'text', text: renderReportText(value, { full: args.reportView === 'full', check: args.check }) }];
62
+ },
63
+ // The card a UI shows, separated from the text the model reads.
64
+ presentationMeta(_args, value) {
65
+ return reportCard(value, MODEL_VERIFY_TOOL_NAME);
53
66
  },
54
67
  },
68
+ presentResult: reportResultView(MODEL_VERIFY_TOOL_NAME),
55
69
  timeoutMs: 10_000,
56
70
  isConcurrencySafe: () => true,
57
71
  async execute(args) {
@@ -71,7 +85,8 @@ export const modelVerifyTool = defineTool({
71
85
  throw new OptionShapeError('VERIFY_OPTION_INVALID', 'stateMapping.' + from + ' is not a state id (it is ' + (Array.isArray(to) ? 'an array' : to === null ? 'null' : 'a ' + typeof to) + '). Every value is the AFTER state id the BEFORE state maps to, so it has to be a non-empty string.');
72
86
  }
73
87
  }
74
- return runVerification(args.model, {
88
+ const model = oneOf(args.model, args.modelPath, 'model', 'modelPath', 'VERIFY_OPTION_INVALID');
89
+ return runVerification(model, {
75
90
  maxStates: args.maxStates,
76
91
  maxPermutationEvents: args.maxPermutationEvents,
77
92
  beforeModel: args.beforeModel,
@@ -0,0 +1,2 @@
1
+ export declare const MODEL_AUTOMATON_TOOL_NAME = "model_automaton_verify";
2
+ export declare const modelAutomatonTool: import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,57 @@
1
+ export interface AutomatonTransition {
2
+ from: string;
3
+ symbol: string;
4
+ to: string;
5
+ }
6
+ export interface Automaton {
7
+ states: string[];
8
+ alphabet: string[];
9
+ transitions: AutomatonTransition[];
10
+ init: string;
11
+ accepting: string[];
12
+ }
13
+ export interface AutomatonFinding {
14
+ code: string;
15
+ severity: 'error' | 'warning' | 'info';
16
+ message: string;
17
+ detail?: string;
18
+ }
19
+ /** Validate the shape, so a refusal says which field is wrong rather than throwing later. */
20
+ export declare function validateAutomaton(input: unknown): {
21
+ ok: true;
22
+ automaton: Automaton;
23
+ } | {
24
+ ok: false;
25
+ errors: string[];
26
+ };
27
+ export type AutomatonRelation = 'equivalent' | 'subset' | 'superset';
28
+ export interface AutomatonReport {
29
+ ok: boolean;
30
+ ran: boolean;
31
+ verdict: 'pass' | 'pass_with_findings' | 'fail';
32
+ verdictReason: string;
33
+ schema: string;
34
+ automaton: {
35
+ states: number;
36
+ transitions: number;
37
+ alphabet: number;
38
+ deterministic: boolean;
39
+ };
40
+ findings: AutomatonFinding[];
41
+ /** Set when the relation fails: the shortest word that breaks it. */
42
+ witness?: {
43
+ word: string[];
44
+ reason: string;
45
+ };
46
+ /** True when the product search stopped before deciding, so `pass` is not a proof of equality. */
47
+ truncated?: boolean;
48
+ hashes: {
49
+ automaton: string;
50
+ other?: string;
51
+ };
52
+ nextSteps: string[];
53
+ }
54
+ export declare function verifyAutomaton(input: unknown, options?: {
55
+ other?: unknown;
56
+ relation?: AutomatonRelation;
57
+ }): AutomatonReport;
@@ -0,0 +1,2 @@
1
+ export declare const MODEL_EDIT_TOOL_NAME = "model_edit";
2
+ export declare const modelEditTool: import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,46 @@
1
+ type Json = Record<string, unknown>;
2
+ export interface EditOperation {
3
+ op: string;
4
+ [key: string]: unknown;
5
+ }
6
+ export interface ModelChange {
7
+ kind: 'added' | 'removed' | 'updated';
8
+ what: string;
9
+ detail: string;
10
+ }
11
+ /** Read the model at `path`; a missing file is a refusal, not an empty model. */
12
+ export declare function readModelFile(path: unknown): {
13
+ model: Json;
14
+ full: string;
15
+ };
16
+ /**
17
+ * Accept the compact spelling of an operation as well as the long one.
18
+ *
19
+ * An operation is a small thing - add a state, wire a transition - and the long form spends more
20
+ * tokens on key names than on the change. Five operations cost 76 tokens spelled out and 39 in the
21
+ * compact form, and a batch of twenty is where that difference starts to matter.
22
+ *
23
+ * {addState: "c"} {addState: {id: "c", terminal: true}}
24
+ * {removeState: "c"}
25
+ * {addTransition: ["a", "reset", "c"]} {addTransition: {from, event, to, guard?, ...}}
26
+ * {removeTransition: ["a", "go"]} {updateTransition: ["a", "go", {guard: ...}]}
27
+ * {set: ["/init", "a"]} {remove: "/init"}
28
+ *
29
+ * The long form is unchanged and is what the errors talk about.
30
+ */
31
+ export declare function normalizeOperation(raw: unknown): EditOperation;
32
+ /** Apply one operation to a model copy, returning the changes it made. */
33
+ export declare function applyOperation(model: Json, operation: EditOperation): ModelChange[];
34
+ /** Validate the edited model and write it atomically. A refused edit leaves the file untouched. */
35
+ export interface EditOutcome {
36
+ model: Json;
37
+ changes: ModelChange[];
38
+ full: string;
39
+ /** Whether the model as written validates. False only when the caller allowed it. */
40
+ valid: boolean;
41
+ errors: string[];
42
+ }
43
+ export declare function applyEdits(path: unknown, operations: unknown, options?: {
44
+ allowInvalid?: boolean;
45
+ }): EditOutcome;
46
+ export {};
@@ -314,6 +314,7 @@ export declare const REPORT_SCHEMAS: {
314
314
  readonly granularity: 'model-everything/granularity/v1';
315
315
  readonly design: 'model-everything/design/v1';
316
316
  readonly baseline: 'model-everything/baseline/v1';
317
+ readonly edit: 'model-everything/edit/v1';
317
318
  };
318
319
  export type ReportSchema = typeof REPORT_SCHEMAS[keyof typeof REPORT_SCHEMAS];
319
320
  /**
@@ -0,0 +1,7 @@
1
+ /** Read and parse one JSON file. Formatting is irrelevant: whitespace, BOM and pretty printing all parse. */
2
+ export declare function readJsonFile(path: unknown, option: string, code: string): unknown;
3
+ /**
4
+ * Exactly one of the inline value and the file path. Both is ambiguous, neither is a missing
5
+ * argument, and each gets its own sentence so the caller knows which one to drop.
6
+ */
7
+ export declare function oneOf(value: unknown, path: unknown, inlineName: string, pathName: string, code: string): unknown;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * What a UI shows for a report, separated from what the model reads.
3
+ *
4
+ * A report is written for a model: it names every check, every finding and the next step, because the
5
+ * model has to act on all of it. A reader wants four things instead - did it run, what does it decide,
6
+ * what is the first thing wrong, what to do next - and wants the rest folded away.
7
+ *
8
+ * Two host hooks carry that split:
9
+ *
10
+ * - `output.presentationMeta` projects a small JSON payload the host persists on the result; a client
11
+ * card narrows it without parsing prose.
12
+ * - `presentResult` returns the card body from that payload, so a UI that supports it shows the summary
13
+ * and a UI that does not falls back to the raw text. The model-facing content never changes.
14
+ *
15
+ * Both are pure and replayable: they read the persisted value, never the filesystem.
16
+ */
17
+ import type { ContentBlock } from '@deepseek-ai/dsh-llm';
18
+ import type { ToolResultView } from '@deepseek-ai/dsh-tools';
19
+ /** One finding, as a card needs it: a code to name it and a sentence to read. */
20
+ export interface CardFinding {
21
+ code: string;
22
+ severity: string;
23
+ message: string;
24
+ }
25
+ /** The payload a client card narrows. Small on purpose: a card is a summary, not a second report. */
26
+ export interface ReportCard {
27
+ verdict?: string;
28
+ verdictReason?: string;
29
+ errors: number;
30
+ warnings: number;
31
+ checksRun?: number;
32
+ first: CardFinding | null;
33
+ /** The one step a reader should take, already a sentence. */
34
+ next: string | null;
35
+ /** Which family produced this, so a card can pick an icon and a colour without parsing codes. */
36
+ family: string;
37
+ /** What this family owns, and where the case just outside it goes. One table, not a second prose copy. */
38
+ route?: {
39
+ owns: string;
40
+ instead: string;
41
+ };
42
+ }
43
+ /**
44
+ * Project a report into the card payload.
45
+ *
46
+ * Read defensively: this runs on replay too, and a payload that once had a shape may come back with a
47
+ * field missing. A card that throws would take the whole session view with it.
48
+ */
49
+ export declare function reportCard(value: unknown, family: string): ReportCard;
50
+ /** The card body: four lines, in the order a reader asks the questions. */
51
+ export declare function cardBlocks(card: ReportCard): ContentBlock[];
52
+ /**
53
+ * `presentResult` for a report tool: render the card from the persisted payload, and fall back to the
54
+ * raw content by returning undefined when there is none.
55
+ */
56
+ export declare function reportResultView(family: string): (_args: unknown, result: {
57
+ content: ContentBlock[];
58
+ meta?: unknown;
59
+ }) => ToolResultView | undefined;
@@ -12,6 +12,13 @@ export interface ReportViewOptions {
12
12
  * matches on check + code + evidence, so a body cannot stand in for the object.
13
13
  */
14
14
  full?: boolean;
15
+ /**
16
+ * Print one check in full: every finding, evidence included, without the per-check budget the
17
+ * digest applies. The digest truncates a check's warnings and tells the reader to ask for it by
18
+ * id; before this option existed that sentence named something the tools could not do, and the
19
+ * only way to see the lines was `full`, which returns the whole structured report.
20
+ */
21
+ check?: string | undefined;
15
22
  }
16
23
  type Record_ = Record<string, unknown>;
17
24
  /** One normalized finding. `evidence` is kept because a finding's identity depends on it. */
@@ -22,7 +29,7 @@ export interface ViewFinding {
22
29
  evidence?: Record_;
23
30
  file?: string;
24
31
  line?: number;
25
- check?: string;
32
+ check?: string | undefined;
26
33
  }
27
34
  /** Normalize one raw finding. Unknown fields are ignored; a missing message renders as the code. */
28
35
  export declare function toViewFinding(value: unknown, check?: string): ViewFinding;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Which tool owns which question, in one table.
3
+ *
4
+ * Two consumers read this, and they used to read two different things: the result a model gets
5
+ * (`nextSteps` naming a sibling) and the toolset map a human reads (hand-written prose). That is how the
6
+ * map came to say "about ten thousand characters" while the descriptions measured twenty-nine: a fact
7
+ * stated twice drifts, and nothing fails when it does.
8
+ *
9
+ * So the table states the routing once. `routeFor` hands it to the card a UI draws; the toolset map
10
+ * generator reads the same table and refuses to write a map for a tool that is missing from it.
11
+ *
12
+ * What it is not: a decision. The tool a request should use is chosen by the model from the descriptions.
13
+ * This table says what each tool owns and which neighbour takes the case just outside that line, so a
14
+ * report can redirect without a reader having to remember the roster.
15
+ */
16
+ export interface Route {
17
+ /** What this tool decides, in the words its report already uses. */
18
+ owns: string;
19
+ /** The case just outside the line, and the tool that owns it. */
20
+ notFor: {
21
+ when: string;
22
+ tool: string;
23
+ };
24
+ }
25
+ export declare const ROUTES: Record<string, Route>;
26
+ /** The route for a tool, or undefined when the tool has none. */
27
+ export declare function routeFor(tool: string): Route | undefined;
28
+ /** Every tool a route names, for a caller that has to check the table against the registry. */
29
+ export declare function routedTools(): string[];
30
+ /** The tools named as the owner of an adjacent case. */
31
+ export declare function siblingTools(): string[];
@@ -0,0 +1,37 @@
1
+ export interface ScannedEdge {
2
+ from: string;
3
+ to: string;
4
+ }
5
+ export interface ScannedGraph {
6
+ /** Module ids, posix-style and relative to the scanned root. File ids for file languages, directory ids for package languages. */
7
+ nodes: string[];
8
+ edges: ScannedEdge[];
9
+ /** Files read, and files skipped because they are not source. */
10
+ filesRead: number;
11
+ /** Relative specifiers that pointed outside the root or at a file that does not exist. */
12
+ unresolved: string[];
13
+ /** Bare specifiers seen (a package name), counted once each. */
14
+ packages: string[];
15
+ /** Which languages were scanned, and at what granularity. */
16
+ languages: {
17
+ language: string;
18
+ files: number;
19
+ granularity: 'file' | 'package';
20
+ }[];
21
+ }
22
+ export type LangGranularity = 'file' | 'package';
23
+ export interface Language {
24
+ name: string;
25
+ extensions: string[];
26
+ granularity: LangGranularity;
27
+ /** Specifiers this file names, already separated from external ones. */
28
+ extract(text: string): {
29
+ local: string[];
30
+ external: string[];
31
+ };
32
+ }
33
+ /** Every language this scan understands, by extension. */
34
+ export declare const LANGUAGES: Language[];
35
+ /** Which language a file belongs to, if any. */
36
+ export declare function languageOf(path: string): Language | undefined;
37
+ export declare function scanDependencyGraph(root: unknown): ScannedGraph;
@@ -95,6 +95,13 @@ export interface StructureSummary {
95
95
  errors: number;
96
96
  warnings: number;
97
97
  rules: number;
98
+ /** The most depended-on nodes, by incoming edges. What a reader asks first and counts worst. */
99
+ hubs: {
100
+ id: string;
101
+ dependents: number;
102
+ }[];
103
+ /** Modules nothing depends on (no outgoing edges), capped for the report. */
104
+ leaves: string[];
98
105
  }
99
106
  export interface StructureReport {
100
107
  /** The tool ran and produced this report; read `verdict` for the outcome. */
@@ -142,6 +149,15 @@ export declare function shortestCycle(nodes: string[], edges: StructureEdge[]):
142
149
  export interface StructureReviewOptions {
143
150
  /** Diagram text to review. */
144
151
  diagram: string;
152
+ /** A graph built from source, instead of parsed from a diagram. `diagram` is then ignored. */
153
+ graph?: StructureGraph;
154
+ /** How the graph was produced, when it came from a scan rather than a drawing. */
155
+ scan?: {
156
+ files: number;
157
+ unresolved: number;
158
+ packages: number;
159
+ languages: string[];
160
+ };
145
161
  notation?: 'auto' | StructureNotation;
146
162
  /** Dependency matrix: allowed/denied edges, layers, required edges. */
147
163
  matrix?: unknown;
package/lib/uml-tool.js CHANGED
@@ -3,6 +3,7 @@ import { createHash } from 'node:crypto';
3
3
  import { REPORT_SCHEMAS, DEFAULT_HASH_SPEC, valueHash, refusalVerdict, verdictOf, verdictOfFindings } from './engine.js';
4
4
  import { renderUml, parseUml, parseFindings, reviewUml, explainLabels, checkUmlIntent, UmlError, UML_DIAGRAMS, } from './uml.js';
5
5
  import { renderReportText } from './report-view.js';
6
+ import { reportCard, reportResultView } from './presentation.js';
6
7
  /**
7
8
  * The UML front end as four tools, one per job.
8
9
  *
@@ -41,13 +42,18 @@ const intentParameter = {
41
42
  enum: ['present', 'verify', 'fidelity'],
42
43
  description: 'What the artifact is for, declared up front: present = a human will read it; verify = it must be readable as a model by the verification engine; fidelity = the render-and-reparse proof is the point. A view that cannot serve the declared intent is refused before any work, with errorCode UML_INTENT_UNSERVABLE, UML_VIEW_UNREPRESENTABLE or UML_INTENT_MISMATCH. Omit it and nothing changes.',
43
44
  };
44
- const reportOutput = {
45
+ const reportOutput = (family) => ({
45
46
  schema: { type: 'json', description: 'The result: diagram source, a family model, or a modelling review report with findings, round-trip fidelity and next steps.' },
46
47
  render(args, value) {
47
48
  // A rendered diagram is source text: the body prints it verbatim, once.
48
49
  return [{ type: 'text', text: renderReportText(value, { full: args.reportView === 'full' }) }];
49
50
  },
50
- };
51
+ // The card a UI shows, separated from the text the model reads. The family is the tool's own name, so
52
+ // one shared output does not have to guess which tool is calling it.
53
+ presentationMeta(_args, value) {
54
+ return reportCard(value, family);
55
+ },
56
+ });
51
57
  /** Draw a model as diagram source. */
52
58
  export const umlRenderTool = defineTool({
53
59
  name: UML_RENDER_TOOL_NAME,
@@ -60,7 +66,8 @@ export const umlRenderTool = defineTool({
60
66
  intent: intentParameter,
61
67
  reportView: reportViewParameter,
62
68
  },
63
- output: reportOutput,
69
+ output: reportOutput(UML_RENDER_TOOL_NAME),
70
+ presentResult: reportResultView(UML_RENDER_TOOL_NAME),
64
71
  timeoutMs: 10_000,
65
72
  isConcurrencySafe: () => true,
66
73
  async execute(args) {
@@ -97,7 +104,8 @@ export const umlParseTool = defineTool({
97
104
  intent: intentParameter,
98
105
  reportView: reportViewParameter,
99
106
  },
100
- output: reportOutput,
107
+ output: reportOutput(UML_PARSE_TOOL_NAME),
108
+ presentResult: reportResultView(UML_PARSE_TOOL_NAME),
101
109
  timeoutMs: 10_000,
102
110
  isConcurrencySafe: () => true,
103
111
  async execute(args) {
@@ -148,7 +156,8 @@ export const umlReviewTool = defineTool({
148
156
  intent: intentParameter,
149
157
  reportView: reportViewParameter,
150
158
  },
151
- output: reportOutput,
159
+ output: reportOutput(UML_REVIEW_TOOL_NAME),
160
+ presentResult: reportResultView(UML_REVIEW_TOOL_NAME),
152
161
  timeoutMs: 10_000,
153
162
  isConcurrencySafe: () => true,
154
163
  async execute(args) {
@@ -179,7 +188,8 @@ export const umlExplainLabelsTool = defineTool({
179
188
  notation: notationParameter,
180
189
  reportView: reportViewParameter,
181
190
  },
182
- output: reportOutput,
191
+ output: reportOutput(UML_LABELS_TOOL_NAME),
192
+ presentResult: reportResultView(UML_LABELS_TOOL_NAME),
183
193
  timeoutMs: 10_000,
184
194
  isConcurrencySafe: () => true,
185
195
  async execute(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-model-everything",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Modeling abstraction and automated review for designs and code, including a refactoring harness: UML diagrams, executable verification of machines and protocols with before/after regression, data-model and migration checks, concurrency claim routing, export to external checkers, dependency-graph review, and report diffing. Native plugin for DeepSeek Harness (dsh).",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -64,8 +64,9 @@
64
64
  "typecheck": "tsc -p tsconfig.json --noEmit",
65
65
  "lint:md": "markdownlint --dot --ignore \"**/node_modules/**\" .",
66
66
  "bump": "node scripts/bump-version.mjs",
67
- "test:engine": "npm run build && node tests/engine/run.mjs && node tests/design-engine/run.mjs && node tests/catalog/run.mjs && node tests/data-engine/run.mjs && node tests/concurrency/run.mjs && node tests/uml/run.mjs && node tests/apply-smoke.mjs && node tests/memory/run.mjs && node tests/skills/run.mjs && node tests/gate/run.mjs && node tests/skill-routing/run.mjs && node tests/toolset/run.mjs && node tests/principles/run.mjs && node tests/feature-checklist/run.mjs && node tests/usage/run.mjs && node tests/translation/run.mjs && node tests/documentation/run.mjs && node tests/contributing/run.mjs && node tests/routing-map/run.mjs && node tests/report-reading/run.mjs && node tests/structure/run.mjs && node tests/baseline/run.mjs && node tests/granularity/run.mjs && node tests/exporters/run.mjs && node tests/report-view/run.mjs && node tests/contracts/run.mjs && node tests/tool-boundary/run.mjs && node tests/negatives/run.mjs && node tests/external/run.mjs",
68
- "test:full": "npm run build && node tests/full-suite.mjs"
67
+ "test:engine": "npm run build && node tests/engine/run.mjs && node tests/design-engine/run.mjs && node tests/catalog/run.mjs && node tests/data-engine/run.mjs && node tests/concurrency/run.mjs && node tests/uml/run.mjs && node tests/apply-smoke.mjs && node tests/memory/run.mjs && node tests/skills/run.mjs && node tests/gate/run.mjs && node tests/skill-routing/run.mjs && node tests/toolset/run.mjs && node tests/principles/run.mjs && node tests/feature-checklist/run.mjs && node tests/usage/run.mjs && node tests/translation/run.mjs && node tests/documentation/run.mjs && node tests/contributing/run.mjs && node tests/routing-map/run.mjs && node tests/report-reading/run.mjs && node tests/structure/run.mjs && node tests/automaton/run.mjs && node tests/presentation/run.mjs && node tests/routing/run.mjs && node tests/tool-routing/run.mjs && node tests/tools/run.mjs && node tests/baseline/run.mjs && node tests/granularity/run.mjs && node tests/exporters/run.mjs && node tests/report-view/run.mjs && node tests/contracts/run.mjs && node tests/tool-boundary/run.mjs && node tests/negatives/run.mjs && node tests/external/run.mjs",
68
+ "test:full": "npm run build && node tests/full-suite.mjs",
69
+ "check": "npm run lint:md && npm run test:engine"
69
70
  },
70
71
  "peerDependencies": {
71
72
  "@deepseek-ai/cordis": "^4.0.4",
@@ -104,6 +105,10 @@
104
105
  },
105
106
  "license": "MIT",
106
107
  "repository": "https://github.com/AmethystLuna/dsh-model-everything",
108
+ "funding": {
109
+ "type": "custom",
110
+ "url": "https://afdian.com/a/amethystluna"
111
+ },
107
112
  "keywords": [
108
113
  "verification",
109
114
  "model-checking",