dsh-model-everything 0.1.0 → 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/README.en-US.md +21 -8
- package/README.md +21 -8
- package/lib/compose-tool.js +5 -1
- package/lib/data-engine.js +1 -1
- package/lib/data-tool.js +12 -4
- package/lib/design-tool.js +14 -6
- package/lib/engine.js +1 -1
- package/lib/model-file.js +48 -0
- package/lib/report-view.js +30 -5
- package/lib/tool.js +13 -4
- package/lib/types/model-file.d.ts +7 -0
- package/lib/types/report-view.d.ts +8 -1
- package/package.json +5 -1
- package/skills/model-everything/SKILL.md +47 -79
- package/skills/model-everything-interleaving/SKILL.zh-CN.md +60 -60
- package/skills/model-everything-report-reading/SKILL.md +137 -137
- package/src/compose-tool.ts +5 -1
- package/src/data-engine.ts +1 -1
- package/src/data-tool.ts +12 -4
- package/src/design-tool.ts +14 -6
- package/src/engine.ts +1 -1
- package/src/model-file.ts +49 -0
- package/src/report-view.ts +483 -451
- package/src/tool.ts +13 -4
package/src/compose-tool.ts
CHANGED
|
@@ -38,6 +38,10 @@ export const modelComposeTool = defineTool({
|
|
|
38
38
|
type: 'integer',
|
|
39
39
|
description: 'Maximum composite states to explore. Default 10000.',
|
|
40
40
|
},
|
|
41
|
+
check: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
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.',
|
|
44
|
+
},
|
|
41
45
|
reportView: {
|
|
42
46
|
type: 'string',
|
|
43
47
|
enum: ['digest', 'full'],
|
|
@@ -50,7 +54,7 @@ export const modelComposeTool = defineTool({
|
|
|
50
54
|
description: 'Composition verification report with C1/C2 findings and, when shared locations are declared, C3/C4/C5.',
|
|
51
55
|
},
|
|
52
56
|
render(args, value) {
|
|
53
|
-
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
57
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full', check: args.check }) }]
|
|
54
58
|
},
|
|
55
59
|
},
|
|
56
60
|
timeoutMs: 10_000,
|
package/src/data-engine.ts
CHANGED
|
@@ -1200,7 +1200,7 @@ function dataNextSteps(checks: CheckResult[]): string[] {
|
|
|
1200
1200
|
// check looked at the model. `pass` does not say that, and neither does a list of the failures.
|
|
1201
1201
|
const skipped = checks.filter((check) => check.status === 'skip').map((check) => check.name + ' (' + check.id + ')')
|
|
1202
1202
|
if (skipped.length > 0) {
|
|
1203
|
-
steps.push('
|
|
1203
|
+
steps.push('Not checked: ' + skipped.join(', ') + ' examined nothing — silence there is not a pass. Declare what each needs, or treat the model as unverified on those points.')
|
|
1204
1204
|
}
|
|
1205
1205
|
if (warningCodes.length > 0) steps.push('Review the warning findings (' + warningCodes.join(', ') + '): a nullable field or a missing index is a decision, not silence.')
|
|
1206
1206
|
steps.push('Re-run with beforeModel plus the mapping/copy/migration options after the change to prove the data behaviour did not regress: migration coverage, copy consistency, rollback symmetry, and the before/after comparison. Then compare the two reports with `report_diff` rather than reading them side by side.')
|
package/src/data-tool.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
2
2
|
import type { JsonValue } from './json-value.js'
|
|
3
3
|
import { runDataVerification, DATA_ENGINE_SCHEMA_VERSION } from './data-engine.js'
|
|
4
|
+
import { oneOf } from './model-file.js'
|
|
4
5
|
import { optionRefusal, requireArrayOption, requireObjectOption } from './option-shape.js'
|
|
5
6
|
import { renderReportText } from './report-view.js'
|
|
6
7
|
|
|
@@ -19,8 +20,11 @@ export const modelDataModelVerifyTool = defineTool({
|
|
|
19
20
|
parameters: {
|
|
20
21
|
model: {
|
|
21
22
|
type: 'json',
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
description: 'DataModelV1 data 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.',
|
|
24
28
|
},
|
|
25
29
|
beforeModel: {
|
|
26
30
|
type: 'json',
|
|
@@ -42,6 +46,10 @@ export const modelDataModelVerifyTool = defineTool({
|
|
|
42
46
|
type: 'json',
|
|
43
47
|
description: 'Optional backup/restore pairs for DA7 rollback symmetry.',
|
|
44
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
|
+
},
|
|
45
53
|
reportView: {
|
|
46
54
|
type: 'string',
|
|
47
55
|
enum: ['digest', 'full'],
|
|
@@ -54,7 +62,7 @@ export const modelDataModelVerifyTool = defineTool({
|
|
|
54
62
|
description: 'model-everything-datamodel verification report with summary and per-check findings.',
|
|
55
63
|
},
|
|
56
64
|
render(args, value) {
|
|
57
|
-
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 }) }]
|
|
58
66
|
},
|
|
59
67
|
},
|
|
60
68
|
timeoutMs: 10_000,
|
|
@@ -67,7 +75,7 @@ export const modelDataModelVerifyTool = defineTool({
|
|
|
67
75
|
requireArrayOption(args.copyPairs, 'copyPairs', 'DATA_OPTION_INVALID')
|
|
68
76
|
requireArrayOption(args.migrationMappings, 'migrationMappings', 'DATA_OPTION_INVALID')
|
|
69
77
|
requireArrayOption(args.backupPairs, 'backupPairs', 'DATA_OPTION_INVALID')
|
|
70
|
-
return runDataVerification(args.model, {
|
|
78
|
+
return runDataVerification(oneOf(args.model, args.modelPath, 'model', 'modelPath', 'DATA_OPTION_INVALID'), {
|
|
71
79
|
beforeModel: args.beforeModel,
|
|
72
80
|
fieldMapping: args.fieldMapping as Record<string, string> | undefined,
|
|
73
81
|
copyPairs: args.copyPairs as Array<{ id: string; sourceEntity: string; targetEntity: string; mapping: Record<string, string> }> | undefined,
|
package/src/design-tool.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
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
|
+
import { oneOf } from './model-file.js'
|
|
4
5
|
import { runDesignReview } from './design-engine.js'
|
|
5
6
|
import { renderReportText } from './report-view.js'
|
|
6
7
|
|
|
@@ -22,10 +23,17 @@ export const designTool = defineTool({
|
|
|
22
23
|
parameters: {
|
|
23
24
|
model: {
|
|
24
25
|
type: 'json',
|
|
25
|
-
required: true,
|
|
26
26
|
description:
|
|
27
27
|
'DesignModelV1: {schemaVersion: 1, architecture: {nodes: [ids], edges: [[from, to]]}, types?: [{name, stereotype, extends?, implements?, operations?, transitions?, forbids?, responsibilities?, dependsOn?, uses?, layer?}], declare?: {layers?, boundaries?, allowedEdges?, requiredEdges?}}. The schema is closed: an unknown key is refused rather than ignored. `types` is what makes the principle checks possible - with no `extends` there is no substitutability to decide, with no `uses` no interface can be called fat, and no `responsibilities` means nothing to compare the dependencies against.',
|
|
28
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.',
|
|
32
|
+
},
|
|
33
|
+
check: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
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.',
|
|
36
|
+
},
|
|
29
37
|
reportView: {
|
|
30
38
|
type: 'string',
|
|
31
39
|
enum: ['digest', 'full'],
|
|
@@ -38,23 +46,23 @@ export const designTool = defineTool({
|
|
|
38
46
|
description: 'Design review report: every check with its status, `violations` (they decide the verdict), `signals`, `facts` (graph features), summary and next steps.',
|
|
39
47
|
},
|
|
40
48
|
render(args, value) {
|
|
41
|
-
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
49
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full', check: args.check }) }]
|
|
42
50
|
},
|
|
43
51
|
},
|
|
44
52
|
timeoutMs: 10_000,
|
|
45
53
|
isConcurrencySafe: () => true,
|
|
46
54
|
async execute(args) {
|
|
47
|
-
if (args.model === undefined) {
|
|
55
|
+
if (args.model === undefined && args.modelPath === undefined) {
|
|
48
56
|
return {
|
|
49
57
|
ok: false,
|
|
50
58
|
ran: false,
|
|
51
59
|
verdict: 'fail',
|
|
52
|
-
verdictReason: 'the tool did not run: pass `model
|
|
60
|
+
verdictReason: 'the tool did not run: pass `model` (a DesignModelV1 with schemaVersion, architecture and the declarations) or `modelPath` (a JSON file holding one)',
|
|
53
61
|
schema: REPORT_SCHEMAS.design,
|
|
54
62
|
errorCode: 'DESIGN_INPUT',
|
|
55
|
-
error: 'design review needs `model`',
|
|
63
|
+
error: 'design review needs `model` or `modelPath`',
|
|
56
64
|
} as unknown as JsonValue
|
|
57
65
|
}
|
|
58
|
-
return runDesignReview(args.model) as unknown as JsonValue
|
|
66
|
+
return runDesignReview(oneOf(args.model, args.modelPath, 'model', 'modelPath', 'DESIGN_OPTION_INVALID')) as unknown as JsonValue
|
|
59
67
|
},
|
|
60
68
|
})
|
package/src/engine.ts
CHANGED
|
@@ -291,7 +291,7 @@ function verificationNextSteps(checks: CheckResult[], coverage: NarrativeCoverag
|
|
|
291
291
|
// named check looked at the model. A list of failures does not say that, and neither does `pass`.
|
|
292
292
|
const skipped = checks.filter((check) => check.status === 'skip').map((check) => check.name + ' (' + check.id + ')')
|
|
293
293
|
if (skipped.length > 0) {
|
|
294
|
-
steps.push('
|
|
294
|
+
steps.push('Not checked: ' + skipped.join(', ') + ' examined nothing — silence there is not a pass. Declare what each needs, or treat the model as unverified on those points.')
|
|
295
295
|
}
|
|
296
296
|
// Named checks above, bare codes here. That is deliberate: each warning is a different kind of thing,
|
|
297
297
|
// so there is no single name to give the set, and the sentence points at where each one IS explained
|
|
@@ -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
|
+
}
|