dsh-model-everything 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.en-US.md +332 -0
- package/README.md +332 -3
- package/cordis.patch.yml +20 -0
- package/examples/README.md +48 -0
- package/examples/control-dispatcher.json +113 -0
- package/examples/control-dispatcher.overbudget.json +113 -0
- package/examples/ecommerce-data-model.json +185 -0
- package/examples/order-state-machine.after.json +116 -0
- package/examples/order-state-machine.before.json +101 -0
- package/examples/user-migration.after.json +29 -0
- package/examples/user-migration.before.json +29 -0
- package/gate/locale/en.json +6 -0
- package/gate/locale/zh.json +6 -0
- package/gate/package.json +5 -0
- package/lib/baseline.js +249 -0
- package/lib/compose-tool.js +80 -0
- package/lib/concurrency-tool.js +40 -0
- package/lib/concurrency.js +206 -0
- package/lib/data-engine.js +1141 -0
- package/lib/data-tool.js +87 -0
- package/lib/design-engine.js +1149 -0
- package/lib/design-tool.js +63 -0
- package/lib/engine.js +4343 -0
- package/lib/export-tool.js +78 -0
- package/lib/exporters.js +434 -0
- package/lib/gate.js +274 -0
- package/lib/granularity.js +237 -0
- package/lib/index.js +237 -0
- package/lib/json-value.js +18 -0
- package/lib/model-file.js +48 -0
- package/lib/option-shape.js +62 -0
- package/lib/report-diff-tool.js +63 -0
- package/lib/report-view.js +433 -0
- package/lib/structure-tool.js +91 -0
- package/lib/structure.js +711 -0
- package/lib/tool.js +94 -0
- package/lib/types/baseline.d.ts +65 -0
- package/lib/types/compose-tool.d.ts +7 -0
- package/lib/types/concurrency-tool.d.ts +8 -0
- package/lib/types/concurrency.d.ts +37 -0
- package/lib/types/data-engine.d.ts +211 -0
- package/lib/types/data-tool.d.ts +10 -0
- package/lib/types/design-engine.d.ts +215 -0
- package/lib/types/design-tool.d.ts +11 -0
- package/lib/types/engine.d.ts +494 -0
- package/lib/types/export-tool.d.ts +7 -0
- package/lib/types/exporters.d.ts +37 -0
- package/lib/types/gate.d.ts +91 -0
- package/lib/types/granularity.d.ts +106 -0
- package/lib/types/index.d.ts +45 -0
- package/lib/types/json-value.d.ts +21 -0
- package/lib/types/model-file.d.ts +7 -0
- package/lib/types/option-shape.d.ts +22 -0
- package/lib/types/report-diff-tool.d.ts +8 -0
- package/lib/types/report-view.d.ts +50 -0
- package/lib/types/structure-tool.d.ts +10 -0
- package/lib/types/structure.d.ts +152 -0
- package/lib/types/tool.d.ts +11 -0
- package/lib/types/uml-communication.d.ts +109 -0
- package/lib/types/uml-composite.d.ts +107 -0
- package/lib/types/uml-interaction-overview.d.ts +91 -0
- package/lib/types/uml-profile.d.ts +112 -0
- package/lib/types/uml-structure.d.ts +53 -0
- package/lib/types/uml-syntax.d.ts +296 -0
- package/lib/types/uml-timing.d.ts +152 -0
- package/lib/types/uml-tool.d.ts +12 -0
- package/lib/types/uml-usecase.d.ts +58 -0
- package/lib/types/uml.d.ts +303 -0
- package/lib/uml-communication.js +353 -0
- package/lib/uml-composite.js +442 -0
- package/lib/uml-interaction-overview.js +277 -0
- package/lib/uml-profile.js +373 -0
- package/lib/uml-structure.js +306 -0
- package/lib/uml-syntax.js +545 -0
- package/lib/uml-timing.js +974 -0
- package/lib/uml-tool.js +213 -0
- package/lib/uml-usecase.js +532 -0
- package/lib/uml.js +2110 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +128 -6
- package/skills/model-everything/SKILL.md +450 -0
- package/skills/model-everything/references/dsh-model-schema.md +437 -0
- package/skills/model-everything/references/gap-routing-guide.md +42 -0
- package/skills/model-everything/references/hash-spec.md +152 -0
- package/skills/model-everything/references/logic-verification-guide.md +422 -0
- package/skills/model-everything-datamodel/SKILL.md +130 -0
- package/skills/model-everything-datamodel/references/data-model-guide.md +84 -0
- package/skills/model-everything-datamodel/references/data-model-schema.md +128 -0
- package/skills/model-everything-design/SKILL.md +115 -0
- package/skills/model-everything-interleaving/SKILL.md +63 -0
- package/skills/model-everything-interleaving/SKILL.zh-CN.md +60 -0
- package/skills/model-everything-interleaving/references/concurrency-risk-guide.md +63 -0
- package/skills/model-everything-report-reading/SKILL.md +137 -0
- package/skills/model-everything-report-reading/references/reading-a-report.md +166 -0
- package/skills/model-everything-report-reading/references/reading-a-report.mmd +59 -0
- package/skills/model-everything-structure/SKILL.md +96 -0
- package/skills/model-everything-structure/references/structure-review-guide.md +201 -0
- package/skills/model-everything-uml/SKILL.md +60 -0
- package/skills/model-everything-uml/references/uml-modeling-guide.md +551 -0
- package/src/baseline.ts +288 -0
- package/src/compose-tool.ts +82 -0
- package/src/concurrency-tool.ts +44 -0
- package/src/concurrency.ts +254 -0
- package/src/data-engine.ts +1208 -0
- package/src/data-tool.ts +91 -0
- package/src/design-engine.ts +1263 -0
- package/src/design-tool.ts +68 -0
- package/src/engine.ts +4564 -0
- package/src/export-tool.ts +81 -0
- package/src/exporters.ts +399 -0
- package/src/gate.ts +330 -0
- package/src/granularity.ts +312 -0
- package/src/index.ts +289 -0
- package/src/json-value.ts +20 -0
- package/src/model-file.ts +49 -0
- package/src/option-shape.ts +64 -0
- package/src/report-diff-tool.ts +67 -0
- package/src/report-view.ts +483 -0
- package/src/structure-tool.ts +93 -0
- package/src/structure.ts +786 -0
- package/src/tool.ts +103 -0
- package/src/uml-communication.ts +428 -0
- package/src/uml-composite.ts +520 -0
- package/src/uml-interaction-overview.ts +321 -0
- package/src/uml-profile.ts +439 -0
- package/src/uml-structure.ts +317 -0
- package/src/uml-syntax.ts +764 -0
- package/src/uml-timing.ts +1106 -0
- package/src/uml-tool.ts +230 -0
- package/src/uml-usecase.ts +566 -0
- package/src/uml.ts +2139 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-model-everything — DeepSeek Harness native plugin for models and diagrams.
|
|
3
|
+
*
|
|
4
|
+
* It ships a skill set (one entry skill plus a skill per domain) and the tools that do
|
|
5
|
+
* the work: UML diagrams render, parse and review; state machines, protocols and composed
|
|
6
|
+
* machines are checked by running them; data models and migrations are checked by the same
|
|
7
|
+
* doctrine; concurrency claims are mined and routed; a verified model is exported to an
|
|
8
|
+
* external checker; a structure diagram is audited as a dependency graph; two reports are
|
|
9
|
+
* diffed against each other.
|
|
10
|
+
*
|
|
11
|
+
* Every tool carries its own description and every report says what it proves, so a model
|
|
12
|
+
* reaches for one the way it reaches for any other tool: by reading what it says it does.
|
|
13
|
+
* The plugin writes nothing into a session.
|
|
14
|
+
*
|
|
15
|
+
* The skills ship in this package's `skills/` directory and are registered at apply time
|
|
16
|
+
* into dsh's `ctx.skills` registry through the standard filesystem provider, so they appear
|
|
17
|
+
* in every session catalog without a manual copy step.
|
|
18
|
+
*
|
|
19
|
+
* Method, doctrine and per-domain detail live in the skills and their references, not here.
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-model-everything
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { fileURLToPath } from 'node:url'
|
|
25
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
26
|
+
import z from '@deepseek-ai/schemastery'
|
|
27
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
28
|
+
import type { ContextFormed } from '@deepseek-ai/dsh-llm'
|
|
29
|
+
import type { Session, UserMessage } from '@deepseek-ai/dsh-session'
|
|
30
|
+
import type { HostCordisInspectProviderRegistration } from '@deepseek-ai/dsh-cordis-host-runner'
|
|
31
|
+
import type { AssembleContext, PromptContext } from '@deepseek-ai/dsh-system-prompt'
|
|
32
|
+
import { FileSystemSkillProvider } from '@deepseek-ai/dsh-skill-filesystem'
|
|
33
|
+
import { modelVerifyTool } from './tool.js'
|
|
34
|
+
import { modelDataModelVerifyTool, DATA_ENGINE_SCHEMA_VERSION } from './data-tool.js'
|
|
35
|
+
import { modelConcurrencyScanTool } from './concurrency-tool.js'
|
|
36
|
+
import { modelComposeTool } from './compose-tool.js'
|
|
37
|
+
import { modelExportTool } from './export-tool.js'
|
|
38
|
+
import { umlRenderTool, umlParseTool, umlReviewTool, umlExplainLabelsTool } from './uml-tool.js'
|
|
39
|
+
import { umlStructureTool } from './structure-tool.js'
|
|
40
|
+
import { designTool } from './design-tool.js'
|
|
41
|
+
import { reportDiffTool } from './report-diff-tool.js'
|
|
42
|
+
import { ENGINE_SCHEMA_VERSION } from './engine.js'
|
|
43
|
+
|
|
44
|
+
// DSH 0.1.7-alpha.1 (session format v4) retires the shared
|
|
45
|
+
// `{ kind: 'plugin', plugin }` wrapper: native admission rejects it in every
|
|
46
|
+
// declared durable message slot, and the official v3-to-v4 migration rewrites
|
|
47
|
+
// those historical rows to `plugin:<name>`. Declaring the producer-owned kind
|
|
48
|
+
// here keeps the write path and the history guard on one identity, and still
|
|
49
|
+
// compiles against the earlier releases that only declare `plugin`.
|
|
50
|
+
|
|
51
|
+
export const name = 'dsh-model-everything'
|
|
52
|
+
|
|
53
|
+
// Skills are contributed through the registry service, which dsh-base always
|
|
54
|
+
// mounts before bundle rows such as this one apply.
|
|
55
|
+
export const inject = ['skills']
|
|
56
|
+
|
|
57
|
+
// Absolute path of the package's shipped skills directory. `lib/index.js`
|
|
58
|
+
// lives one level below the package root, so `../skills` from the module URL
|
|
59
|
+
// lands on `<package>/skills` regardless of where the package was installed.
|
|
60
|
+
const SKILLS_DIR = fileURLToPath(new URL('../skills', import.meta.url))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
export type InteractionMode = 'ask' | 'auto' | 'follow-approval'
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
export interface Config {
|
|
68
|
+
/**
|
|
69
|
+
* How an offer to run a tool resolves: `ask` and `auto` are fixed, while `follow-approval`
|
|
70
|
+
* reads the session's approval policy per call. `resolveInteraction` is its only reader.
|
|
71
|
+
*
|
|
72
|
+
* There is deliberately no on/off key here. A row is switched off by the loader row that
|
|
73
|
+
* mounts it (`disabled`, or the plugin manager), which removes the tools and the skills
|
|
74
|
+
* together; a key that turned the tools off while the skills stayed would leave every skill
|
|
75
|
+
* naming a tool that is not there, which is worse than having no skill at all.
|
|
76
|
+
*/
|
|
77
|
+
interaction: InteractionMode
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const Config = z.object({
|
|
81
|
+
interaction: z.union(['ask', 'auto', 'follow-approval']).default('follow-approval'),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
interface SessionEventLike {
|
|
86
|
+
type: string
|
|
87
|
+
data?: Record<string, unknown>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Minimum session-store surface this plugin reads. DSH 0.1.2-alpha.4 replaced
|
|
92
|
+
* the `Session.events` getter with on-demand reads (`seq`, `eventAt()`,
|
|
93
|
+
* `snapshotEvents()`); releases up to 0.1.2-alpha.3 expose `events` as the
|
|
94
|
+
* full log snapshot. Read through a structural union so one build serves every
|
|
95
|
+
* declared DSH release.
|
|
96
|
+
*/
|
|
97
|
+
interface SessionEventSource {
|
|
98
|
+
events?: readonly SessionEventLike[]
|
|
99
|
+
snapshotEvents?: () => readonly SessionEventLike[]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readSessionEvents(session: Session): readonly SessionEventLike[] {
|
|
103
|
+
const source = session as unknown as SessionEventSource
|
|
104
|
+
if (typeof source.snapshotEvents === 'function') {
|
|
105
|
+
const snapshot = source.snapshotEvents()
|
|
106
|
+
if (Array.isArray(snapshot)) return snapshot
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(source.events)) return source.events
|
|
109
|
+
return []
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function lastApprovalPolicy(session: Session): 'ask' | 'never' | undefined {
|
|
113
|
+
const events = readSessionEvents(session)
|
|
114
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
115
|
+
const event = events[index]
|
|
116
|
+
if (event.type === 'approval/policy') {
|
|
117
|
+
return event.data?.policy === 'never' ? 'never' : 'ask'
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return undefined
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function planModeActive(session: Session): boolean {
|
|
124
|
+
const events = readSessionEvents(session)
|
|
125
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
126
|
+
const event = events[index]
|
|
127
|
+
if (event.type === 'plan/mode') return event.data?.active === true
|
|
128
|
+
}
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolveInteraction(config: Config, session: Session): 'ask' | 'auto' {
|
|
133
|
+
if (config.interaction === 'ask' || config.interaction === 'auto') return config.interaction
|
|
134
|
+
return lastApprovalPolicy(session) === 'never' ? 'auto' : 'ask'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
interface ToolRegistryLike {
|
|
139
|
+
register(definition: unknown): () => void
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface SystemPromptLike {
|
|
143
|
+
context(contribution: PromptContext): () => void
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Model-visible catalog entry (cordis_inspect_list / cordis_inspect_query):
|
|
148
|
+
* lets the model read this plugin's runtime status without guessing. Mirrors
|
|
149
|
+
* the registration pattern of the official dsh-tool-cordis host providers.
|
|
150
|
+
*/
|
|
151
|
+
function inspectProvider(config: Config, isToolRegistered: () => boolean, isDataToolRegistered: () => boolean, isConcurrencyToolRegistered: () => boolean, isComposeToolRegistered: () => boolean, isExportToolRegistered: () => boolean, isUmlToolRegistered: () => boolean, isStructureToolRegistered: () => boolean, isReportDiffToolRegistered: () => boolean): HostCordisInspectProviderRegistration {
|
|
152
|
+
return {
|
|
153
|
+
manifest: {
|
|
154
|
+
id: 'dsh-model-everything',
|
|
155
|
+
description: 'Model and diagram tooling for DeepSeek Harness: UML diagrams (render, read, review), executable verification of state machines, protocols and composed machines, data-model and migration checks, concurrency claim routing, external-checker export, dependency-graph review, and report diffing. The tools carry their own descriptions; the skills carry the doctrine and the per-domain detail.',
|
|
156
|
+
methods: [
|
|
157
|
+
{
|
|
158
|
+
name: 'status',
|
|
159
|
+
description: 'Read this plugin status: whether it registers its tools, which tools are on, and the model schema versions the bundled engines accept.',
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
properties: {},
|
|
163
|
+
additionalProperties: false,
|
|
164
|
+
},
|
|
165
|
+
outputSchema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
description: 'Plugin status: registration state and the schema versions the bundled engines accept.',
|
|
168
|
+
properties: {
|
|
169
|
+
interaction: { type: 'string', enum: ['ask', 'auto', 'follow-approval'], description: 'Configured interaction mode. follow-approval resolves per session from approval/policy.' },
|
|
170
|
+
toolRegistered: { type: 'boolean', description: 'Whether the model_verify tool is registered on ctx.tools.' },
|
|
171
|
+
dataToolRegistered: { type: 'boolean', description: 'Whether the model_datamodel_verify tool is registered on ctx.tools.' },
|
|
172
|
+
concurrencyToolRegistered: { type: 'boolean', description: 'Whether the model_interleaving_scan tool is registered on ctx.tools.' },
|
|
173
|
+
composeToolRegistered: { type: 'boolean', description: 'Whether the model_compose_verify tool is registered on ctx.tools.' },
|
|
174
|
+
exportToolRegistered: { type: 'boolean', description: 'Whether the model_export tool is registered on ctx.tools.' },
|
|
175
|
+
umlToolsRegistered: { type: 'boolean', description: 'Whether the four UML tools (uml_render, uml_parse, uml_review, uml_explain_labels) are registered on ctx.tools.' },
|
|
176
|
+
structureToolRegistered: { type: 'boolean', description: 'Whether the uml_structure_verify tool (structure-diagram / dependency review) is registered on ctx.tools.' },
|
|
177
|
+
reportDiffToolRegistered: { type: 'boolean', description: 'Whether the report_diff tool (baseline report comparison) is registered on ctx.tools.' },
|
|
178
|
+
engineSchemaVersion: { type: 'integer', description: 'Model schema version the bundled state-machine verification engine accepts.' },
|
|
179
|
+
dataEngineSchemaVersion: { type: 'integer', description: 'Model schema version the bundled data-model verification engine accepts.' },
|
|
180
|
+
},
|
|
181
|
+
additionalProperties: false,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
query: async (method) => {
|
|
187
|
+
if (method === 'status') {
|
|
188
|
+
return {
|
|
189
|
+
interaction: config.interaction,
|
|
190
|
+
toolRegistered: isToolRegistered(),
|
|
191
|
+
dataToolRegistered: isDataToolRegistered(),
|
|
192
|
+
concurrencyToolRegistered: isConcurrencyToolRegistered(),
|
|
193
|
+
composeToolRegistered: isComposeToolRegistered(),
|
|
194
|
+
exportToolRegistered: isExportToolRegistered(),
|
|
195
|
+
umlToolsRegistered: isUmlToolRegistered(),
|
|
196
|
+
structureToolRegistered: isStructureToolRegistered(),
|
|
197
|
+
reportDiffToolRegistered: isReportDiffToolRegistered(),
|
|
198
|
+
engineSchemaVersion: ENGINE_SCHEMA_VERSION,
|
|
199
|
+
dataEngineSchemaVersion: DATA_ENGINE_SCHEMA_VERSION,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function apply(ctx: Context, config: Config): void {
|
|
208
|
+
// Optional services are registered opportunistically; base-bundle rows can
|
|
209
|
+
// mount after this row applies, so registration is retried on the first
|
|
210
|
+
// agent/pre-step — by then the app is fully booted.
|
|
211
|
+
let providerRegistered = false
|
|
212
|
+
let toolRegistered = false
|
|
213
|
+
let dataToolRegistered = false
|
|
214
|
+
let concurrencyToolRegistered = false
|
|
215
|
+
let composeToolRegistered = false
|
|
216
|
+
let exportToolRegistered = false
|
|
217
|
+
let umlToolsRegistered = false
|
|
218
|
+
let structureToolRegistered = false
|
|
219
|
+
let reportDiffToolRegistered = false
|
|
220
|
+
const registerProvider = (): void => {
|
|
221
|
+
if (providerRegistered) return
|
|
222
|
+
const inspect = ctx.get('cordisInspect')
|
|
223
|
+
if (inspect === undefined) return
|
|
224
|
+
try {
|
|
225
|
+
ctx.effect(() => inspect.register(inspectProvider(config, () => toolRegistered, () => dataToolRegistered, () => concurrencyToolRegistered, () => composeToolRegistered, () => exportToolRegistered, () => umlToolsRegistered, () => structureToolRegistered, () => reportDiffToolRegistered)), 'dsh-model-everything: inspect provider')
|
|
226
|
+
providerRegistered = true
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.warn('[dsh-model-everything] inspect provider registration failed', err)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const registerTool = (): void => {
|
|
232
|
+
if (toolRegistered) return
|
|
233
|
+
const tools = ctx.get('tools') as ToolRegistryLike | undefined
|
|
234
|
+
if (tools === undefined) return
|
|
235
|
+
try {
|
|
236
|
+
ctx.effect(() => tools.register(modelVerifyTool), 'dsh-model-everything: verify tool')
|
|
237
|
+
ctx.effect(() => tools.register(modelDataModelVerifyTool), 'dsh-model-everything: data verify tool')
|
|
238
|
+
ctx.effect(() => tools.register(modelConcurrencyScanTool), 'dsh-model-everything: concurrency scan tool')
|
|
239
|
+
ctx.effect(() => tools.register(modelComposeTool), 'dsh-model-everything: compose tool')
|
|
240
|
+
ctx.effect(() => tools.register(modelExportTool), 'dsh-model-everything: export tool')
|
|
241
|
+
ctx.effect(() => tools.register(umlRenderTool), 'dsh-model-everything: uml render tool')
|
|
242
|
+
ctx.effect(() => tools.register(umlParseTool), 'dsh-model-everything: uml parse tool')
|
|
243
|
+
ctx.effect(() => tools.register(umlReviewTool), 'dsh-model-everything: uml review tool')
|
|
244
|
+
ctx.effect(() => tools.register(umlExplainLabelsTool), 'dsh-model-everything: uml explain-labels tool')
|
|
245
|
+
ctx.effect(() => tools.register(umlStructureTool), 'dsh-model-everything: structure tool')
|
|
246
|
+
ctx.effect(() => tools.register(designTool), 'dsh-model-everything: design review tool')
|
|
247
|
+
ctx.effect(() => tools.register(reportDiffTool), 'dsh-model-everything: report diff tool')
|
|
248
|
+
toolRegistered = true
|
|
249
|
+
dataToolRegistered = true
|
|
250
|
+
concurrencyToolRegistered = true
|
|
251
|
+
composeToolRegistered = true
|
|
252
|
+
exportToolRegistered = true
|
|
253
|
+
umlToolsRegistered = true
|
|
254
|
+
structureToolRegistered = true
|
|
255
|
+
reportDiffToolRegistered = true
|
|
256
|
+
} catch (err) {
|
|
257
|
+
console.warn('[dsh-model-everything] model_verify/model_datamodel_verify tool registration failed', err)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const registerIntegrations = (): void => {
|
|
261
|
+
registerProvider()
|
|
262
|
+
registerTool()
|
|
263
|
+
}
|
|
264
|
+
registerIntegrations()
|
|
265
|
+
// Ship the bundled skill through the registry: reuse the standard
|
|
266
|
+
// filesystem provider over this package's own `skills/` directory, so
|
|
267
|
+
// catalog discovery, frontmatter parsing, and SKILL.md loading behave
|
|
268
|
+
// exactly like project/user skills while the plugin stays self-contained.
|
|
269
|
+
// Registration lands in the global registry layer (this row mounts at the
|
|
270
|
+
// profile root), so every agent preset sees the skill. `registerProvider`
|
|
271
|
+
// returns the effect disposer; its teardown unregisters and invalidates.
|
|
272
|
+
ctx.skills.registerProvider((control) => {
|
|
273
|
+
return new FileSystemSkillProvider(ctx, control, {
|
|
274
|
+
providerName: 'dsh-model-everything',
|
|
275
|
+
includeDefaultRoots: false,
|
|
276
|
+
customSkillDirs: [SKILLS_DIR],
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
// Optional services register opportunistically and a base-bundle row can mount after
|
|
280
|
+
// this one applies, so registration is retried on the first model step - the point by
|
|
281
|
+
// which the app is fully booted. Nothing is appended to the step: the tools and the
|
|
282
|
+
// skill text are the guide, not an injected reminder. Session-start routing, if a
|
|
283
|
+
// deployment wants it, is the separate gate component in `gate.ts`, not this row.
|
|
284
|
+
ctx.on('agent/pre-step', async (_payload, next) => {
|
|
285
|
+
const decision = await next()
|
|
286
|
+
registerIntegrations()
|
|
287
|
+
return decision
|
|
288
|
+
})
|
|
289
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural mirror of the JSON value union the dsh tool registry accepts.
|
|
3
|
+
*
|
|
4
|
+
* `@deepseek-ai/dsh-tools` exported this type through the 0.1.x line and stopped
|
|
5
|
+
* exporting it in 0.2.x, where the same type now lives in
|
|
6
|
+
* `@deepseek-ai/dsh-util-values`. This package declares peer support for both
|
|
7
|
+
* lines, so it cannot import the type from either one without breaking the other
|
|
8
|
+
* build. Declaring the one-line union here keeps `npm run typecheck` green against
|
|
9
|
+
* both, and it costs nothing at runtime: every use is a type-only cast on a value
|
|
10
|
+
* this package already produced.
|
|
11
|
+
*
|
|
12
|
+
* Keep it structurally identical to the official type. A cast through `unknown` to
|
|
13
|
+
* this alias is only a way to satisfy `defineTool`'s return-type constraint; if the
|
|
14
|
+
* official union ever widens, this alias must widen with it.
|
|
15
|
+
*
|
|
16
|
+
* @module this plugin-json-value
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** JSON-serializable value: null, boolean, number, string, or an array/object of those. */
|
|
20
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a JSON model from a file instead of requiring it inline.
|
|
3
|
+
*
|
|
4
|
+
* A model is the largest argument any of these tools takes, and it is the one that arrives as
|
|
5
|
+
* streamed tool-call JSON: the provider rejected one session with `tool input is invalid JSON`
|
|
6
|
+
* after the model spent sixteen steps hand-editing a nested model argument. Reading it from the
|
|
7
|
+
* workspace removes that failure mode and the tokens.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from 'node:fs'
|
|
10
|
+
import { resolve } from 'node:path'
|
|
11
|
+
|
|
12
|
+
import { OptionShapeError } from './option-shape.js'
|
|
13
|
+
|
|
14
|
+
/** Read and parse one JSON file. Formatting is irrelevant: whitespace, BOM and pretty printing all parse. */
|
|
15
|
+
export function readJsonFile(path: unknown, option: string, code: string): unknown {
|
|
16
|
+
if (typeof path !== 'string' || path.trim() === '') {
|
|
17
|
+
throw new OptionShapeError(code, `${option} must be a non-empty path to a JSON file.`)
|
|
18
|
+
}
|
|
19
|
+
const full = resolve(path)
|
|
20
|
+
let text: string
|
|
21
|
+
try {
|
|
22
|
+
text = readFileSync(full, 'utf8')
|
|
23
|
+
} catch (error) {
|
|
24
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
25
|
+
throw new OptionShapeError(code, `${option} could not be read: ${reason}. The path resolves against the process working directory; relative paths in a session resolve against the session's workspace.`)
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(text.replace(/^\uFEFF/, ''))
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
31
|
+
throw new OptionShapeError(code, `${option} is not valid JSON: ${reason}.`)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Exactly one of the inline value and the file path. Both is ambiguous, neither is a missing
|
|
37
|
+
* argument, and each gets its own sentence so the caller knows which one to drop.
|
|
38
|
+
*/
|
|
39
|
+
export function oneOf(value: unknown, path: unknown, inlineName: string, pathName: string, code: string): unknown {
|
|
40
|
+
const hasValue = value !== undefined
|
|
41
|
+
const hasPath = path !== undefined
|
|
42
|
+
if (hasValue && hasPath) {
|
|
43
|
+
throw new OptionShapeError(code, `Pass either ${inlineName} or ${pathName}, not both: the file would be read and then ignored, or the other way round.`)
|
|
44
|
+
}
|
|
45
|
+
if (!hasValue && !hasPath) {
|
|
46
|
+
throw new OptionShapeError(code, `Pass ${inlineName} or ${pathName}: the model has to come from somewhere.`)
|
|
47
|
+
}
|
|
48
|
+
return hasPath ? readJsonFile(path, pathName, code) : value
|
|
49
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Option shapes at the tool boundary.
|
|
3
|
+
*
|
|
4
|
+
* A tool declares `type: 'json'` for an option, which accepts any JSON value, and the engine then reads
|
|
5
|
+
* it as the type it needs. A string where an array was expected threw `machinesInput.forEach is not a
|
|
6
|
+
* function` and `(options.migrationMappings ?? []).map is not a function` - raw TypeErrors with no code,
|
|
7
|
+
* which a caller cannot route on and which say nothing about which option was wrong.
|
|
8
|
+
*
|
|
9
|
+
* The suite pinned those as known gaps. This closes them the way the rest of the plugin reports a
|
|
10
|
+
* refusal: a code naming the family, and a message naming the option.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** A refusal the caller can route on: the domain's code, and what arrived. */
|
|
14
|
+
export class OptionShapeError extends Error {
|
|
15
|
+
constructor(
|
|
16
|
+
readonly errorCode: string,
|
|
17
|
+
message: string,
|
|
18
|
+
) {
|
|
19
|
+
super(message)
|
|
20
|
+
this.name = 'OptionShapeError'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function describe(value: unknown): string {
|
|
25
|
+
if (value === null) return 'null'
|
|
26
|
+
if (Array.isArray(value)) return 'an array'
|
|
27
|
+
return 'a ' + typeof value
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** An option the engine reads with `.map`: a string or a number is not one. */
|
|
31
|
+
export function requireArrayOption(value: unknown, option: string, errorCode: string): void {
|
|
32
|
+
if (value === undefined) return
|
|
33
|
+
if (!Array.isArray(value)) {
|
|
34
|
+
throw new OptionShapeError(errorCode, option + ' is ' + describe(value) + ', not an array. This option is read as a list; omit it, or pass the list the engine documents.')
|
|
35
|
+
}
|
|
36
|
+
// A null ENTRY fails later, deeper, with a message about a property of null. Naming the index here is the
|
|
37
|
+
// difference between "something is wrong" and "entry 2 is wrong".
|
|
38
|
+
const bad = value.findIndex((entry) => entry === null || typeof entry !== 'object' || Array.isArray(entry))
|
|
39
|
+
if (bad >= 0) {
|
|
40
|
+
throw new OptionShapeError(errorCode, option + '[' + String(bad) + '] is ' + describe(value[bad]) + ', not an object. Each entry is read as a mapping or a pair; a position cannot be omitted.')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** An option the engine reads with `Object.entries`: a string has entries, and they are not the ones meant. */
|
|
45
|
+
export function requireObjectOption(value: unknown, option: string, errorCode: string): void {
|
|
46
|
+
if (value === undefined) return
|
|
47
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
48
|
+
throw new OptionShapeError(errorCode, option + ' is ' + describe(value) + ', not an object. This option is read as a map from one name to another.')
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The refusal a tool returns for a shape it could not read. */
|
|
53
|
+
export function optionRefusal(error: unknown, errorCode: string): Record<string, unknown> {
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
55
|
+
const code = error instanceof OptionShapeError ? error.errorCode : errorCode
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
ran: false,
|
|
59
|
+
verdict: 'fail',
|
|
60
|
+
verdictReason: 'the tool did not run: ' + message,
|
|
61
|
+
errorCode: code,
|
|
62
|
+
error: message,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
2
|
+
import type { JsonValue } from './json-value.js'
|
|
3
|
+
import { diffReports } from './baseline.js'
|
|
4
|
+
import { refusalVerdict } from './engine.js'
|
|
5
|
+
import { renderReportText } from './report-view.js'
|
|
6
|
+
|
|
7
|
+
export const REPORT_DIFF_TOOL_NAME = 'report_diff'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Turn a refusal into a report the model can route on.
|
|
11
|
+
*
|
|
12
|
+
* `diffReports` throws when a side is not a report, which is the right thing at the engine boundary -
|
|
13
|
+
* there is nothing to compare and no verdict to give. The tool boundary has a different obligation:
|
|
14
|
+
* malformed input has to come back as something with a code, never as a raw throw. This is the same
|
|
15
|
+
* shape the UML tools use, with a code of this tool's own.
|
|
16
|
+
*/
|
|
17
|
+
function refusal(error: unknown): JsonValue {
|
|
18
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
19
|
+
return { ok: false, ran: false, ...refusalVerdict(message), errorCode: 'REPORT_INVALID', error: message } as unknown as JsonValue
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Model-visible DSH tool that compares two reports of the same family — the accepted
|
|
24
|
+
* "violations must not increase" acceptance criterion, computed instead of eyeballed.
|
|
25
|
+
* A finding's identity is its check + code + machine-readable locator, so a reworded
|
|
26
|
+
* message reads as `changed`, and two runs over the same input add nothing.
|
|
27
|
+
*/
|
|
28
|
+
export const reportDiffTool = defineTool({
|
|
29
|
+
name: REPORT_DIFF_TOOL_NAME,
|
|
30
|
+
description:
|
|
31
|
+
'Compare two reports of the same family, to accept a change by what it added rather than by reading two reports: `baseline` (earlier) against `current` (newer). Use it to accept a slice by "no new findings". Findings match on a stable identity: check id, code, and a locator built from `evidence` and `path`. Reworded prose counts as `changed`, not as new. Returns added, removed and changed findings, plus `summary`. It does not look at a single artefact and it does not produce a report of its own: for the first look at one, call the domain tool directly (`model_verify`, `model_datamodel_verify`, `model_design_review`, `uml_review`, `uml_structure_verify`, `model_interleaving_scan`), then compare what those two calls returned. `verdict` is about the delta: a newly added error finding fails it. `currentVerdict` keeps the run\'s own verdict, so a clean delta over a failing report still shows as failing. Two runs over the same input add nothing. Both sides have to be reports this can compare: a refusal, a run that examined nothing, and a report from another family are refused with `REPORT_INVALID` instead of being compared, because a finding absent from an incomparable roster would otherwise read as resolved.',
|
|
32
|
+
parameters: {
|
|
33
|
+
baseline: {
|
|
34
|
+
type: 'json',
|
|
35
|
+
required: true,
|
|
36
|
+
description: 'The earlier report (any family: verify, compose, datamodel, uml review, structure, concurrency).',
|
|
37
|
+
},
|
|
38
|
+
current: {
|
|
39
|
+
type: 'json',
|
|
40
|
+
required: true,
|
|
41
|
+
description: 'The current report to compare against the baseline.',
|
|
42
|
+
},
|
|
43
|
+
reportView: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
enum: ['digest', 'full'],
|
|
46
|
+
description: 'digest (default) prints the readable body; full prints the structured diff report.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
output: {
|
|
50
|
+
schema: {
|
|
51
|
+
type: 'json',
|
|
52
|
+
description: 'Baseline diff report: added/removed/changed findings, delta verdict, current verdict, summary and next steps.',
|
|
53
|
+
},
|
|
54
|
+
render(args, value) {
|
|
55
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
timeoutMs: 10_000,
|
|
59
|
+
isConcurrencySafe: () => true,
|
|
60
|
+
async execute(args) {
|
|
61
|
+
try {
|
|
62
|
+
return diffReports(args.baseline, args.current) as unknown as JsonValue
|
|
63
|
+
} catch (error) {
|
|
64
|
+
return refusal(error)
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
})
|