dsh-logicprobe 1.0.0 → 1.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 +93 -85
- package/README.md +93 -85
- package/lib/compose-tool.js +8 -2
- package/lib/concurrency-tool.js +8 -2
- package/lib/data-tool.js +8 -2
- package/lib/engine.js +61 -11
- package/lib/export-tool.js +8 -2
- package/lib/index.js +28 -40
- package/lib/report-diff-tool.js +8 -2
- package/lib/report-view.js +295 -0
- package/lib/structure-tool.js +8 -2
- package/lib/tool.js +8 -2
- package/lib/types/engine.d.ts +6 -1
- package/lib/types/index.d.ts +5 -4
- package/lib/types/report-view.d.ts +43 -0
- package/lib/uml-tool.js +27 -15
- package/lib/uml.js +16 -6
- package/package.json +2 -2
- package/skills/logicprobe/SKILL.md +26 -9
- package/skills/logicprobe/references/dsh-model-schema.md +25 -4
- package/skills/logicprobe/references/gap-routing-guide.md +10 -9
- package/skills/logicprobe/references/hash-spec.md +25 -0
- package/skills/logicprobe/references/logic-verification-guide.md +1 -1
- package/skills/logicprobe-concurrency/SKILL.md +1 -0
- package/skills/logicprobe-uml/SKILL.md +3 -1
- package/skills/logicprobe-uml/references/uml-modeling-guide.md +7 -6
- package/src/compose-tool.ts +8 -2
- package/src/concurrency-tool.ts +8 -2
- package/src/data-tool.ts +9 -3
- package/src/engine.ts +67 -10
- package/src/export-tool.ts +8 -2
- package/src/index.ts +28 -40
- package/src/report-diff-tool.ts +8 -2
- package/src/report-view.ts +343 -0
- package/src/structure-tool.ts +8 -2
- package/src/tool.ts +8 -2
- package/src/uml-tool.ts +27 -15
- package/src/uml.ts +20 -7
package/src/compose-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 { runCompositionVerification } from './engine.js'
|
|
4
|
+
import { renderReportText } from './report-view.js'
|
|
4
5
|
|
|
5
6
|
export const LOGICPROBE_COMPOSE_TOOL_NAME = 'logicprobe_compose_verify'
|
|
6
7
|
|
|
@@ -26,14 +27,19 @@ export const logicProbeComposeTool = defineTool({
|
|
|
26
27
|
type: 'integer',
|
|
27
28
|
description: 'Maximum composite states to explore. Default 10000.',
|
|
28
29
|
},
|
|
30
|
+
reportView: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
enum: ['digest', 'full'],
|
|
33
|
+
description: 'digest (default) prints the readable body; full prints the structured report, which logicprobe_report_diff needs.',
|
|
34
|
+
},
|
|
29
35
|
},
|
|
30
36
|
output: {
|
|
31
37
|
schema: {
|
|
32
38
|
type: 'json',
|
|
33
39
|
description: 'Composition verification report with C1/C2 findings.',
|
|
34
40
|
},
|
|
35
|
-
render(
|
|
36
|
-
return [{ type: 'text' as const, text:
|
|
41
|
+
render(args, value) {
|
|
42
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
37
43
|
},
|
|
38
44
|
},
|
|
39
45
|
timeoutMs: 10_000,
|
package/src/concurrency-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 { runConcurrencyScan } from './concurrency.js'
|
|
4
|
+
import { renderReportText } from './report-view.js'
|
|
4
5
|
|
|
5
6
|
export const LOGICPROBE_CONCURRENCY_SCAN_TOOL_NAME = 'logicprobe_concurrency_scan'
|
|
6
7
|
|
|
@@ -20,14 +21,19 @@ export const logicProbeConcurrencyScanTool = defineTool({
|
|
|
20
21
|
required: true,
|
|
21
22
|
description: 'Document or plan text to scan for concurrency-related claims.',
|
|
22
23
|
},
|
|
24
|
+
reportView: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
enum: ['digest', 'full'],
|
|
27
|
+
description: 'digest (default) prints the readable body; full prints the structured report, which logicprobe_report_diff needs.',
|
|
28
|
+
},
|
|
23
29
|
},
|
|
24
30
|
output: {
|
|
25
31
|
schema: {
|
|
26
32
|
type: 'json',
|
|
27
33
|
description: 'Concurrency scan report with findings and summary.',
|
|
28
34
|
},
|
|
29
|
-
render(
|
|
30
|
-
return [{ type: 'text' as const, text:
|
|
35
|
+
render(args, value) {
|
|
36
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
31
37
|
},
|
|
32
38
|
},
|
|
33
39
|
timeoutMs: 10_000,
|
package/src/data-tool.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
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 { renderReportText } from './report-view.js'
|
|
4
5
|
|
|
5
6
|
export const LOGICPROBE_DATAMODEL_VERIFY_TOOL_NAME = 'logicprobe_datamodel_verify'
|
|
6
7
|
|
|
@@ -40,14 +41,19 @@ export const logicProbeDataModelVerifyTool = defineTool({
|
|
|
40
41
|
type: 'json',
|
|
41
42
|
description: 'Optional backup/restore pairs for DA7 rollback symmetry.',
|
|
42
43
|
},
|
|
44
|
+
reportView: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
enum: ['digest', 'full'],
|
|
47
|
+
description: 'digest (default) prints the readable body; full prints the structured report, which logicprobe_report_diff needs.',
|
|
48
|
+
},
|
|
43
49
|
},
|
|
44
50
|
output: {
|
|
45
51
|
schema: {
|
|
46
52
|
type: 'json',
|
|
47
53
|
description: 'logicprobe-datamodel verification report with summary and per-check findings.',
|
|
48
54
|
},
|
|
49
|
-
render(
|
|
50
|
-
return [{ type: 'text' as const, text:
|
|
55
|
+
render(args, value) {
|
|
56
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
51
57
|
},
|
|
52
58
|
},
|
|
53
59
|
timeoutMs: 10_000,
|
package/src/engine.ts
CHANGED
|
@@ -206,7 +206,11 @@ export interface PathStep {
|
|
|
206
206
|
|
|
207
207
|
export interface Finding {
|
|
208
208
|
code: string
|
|
209
|
-
|
|
209
|
+
/**
|
|
210
|
+
* `info` is a note, not a defect: it is reported so a machine-readable reader can see it,
|
|
211
|
+
* and it never contributes to `verdict`, `summary.errors` or `summary.warnings`.
|
|
212
|
+
*/
|
|
213
|
+
severity: 'error' | 'warning' | 'info'
|
|
210
214
|
message: string
|
|
211
215
|
/** Longer explanation: the shortest-path note, the per-machine reasons, the quoted source lines. */
|
|
212
216
|
detail?: string
|
|
@@ -286,6 +290,20 @@ function verificationNextSteps(checks: CheckResult[], coverage: NarrativeCoverag
|
|
|
286
290
|
return steps
|
|
287
291
|
}
|
|
288
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Repair guidance for a rejected model. A refusal reports the offending path and the rule
|
|
295
|
+
* it broke, but not what to write instead; for fields whose absence is a documented trap,
|
|
296
|
+
* that is the difference between "fix it" and "guess again". Derived only from the
|
|
297
|
+
* validation messages, so the list stays deterministic.
|
|
298
|
+
*/
|
|
299
|
+
function refusalGuidance(errors: string[]): string[] {
|
|
300
|
+
const steps: string[] = []
|
|
301
|
+
if (errors.some((message) => message.includes('.description'))) {
|
|
302
|
+
steps.push('Add the required "description" string to every invariant (for example "description": "what this invariant guarantees"); a `_`-prefixed key is not a substitute, and the value takes part in `modelHash`.')
|
|
303
|
+
}
|
|
304
|
+
return steps
|
|
305
|
+
}
|
|
306
|
+
|
|
289
307
|
export interface ComparisonSummary {
|
|
290
308
|
beforeModelHash: string
|
|
291
309
|
afterModelHash: string
|
|
@@ -366,6 +384,7 @@ export const REPORT_SCHEMAS = {
|
|
|
366
384
|
umlRender: 'logicprobe/uml/render/v1',
|
|
367
385
|
umlParse: 'logicprobe/uml/parse/v1',
|
|
368
386
|
umlReview: 'logicprobe/uml/review/v1',
|
|
387
|
+
umlExplainLabels: 'logicprobe/uml/explain-labels/v1',
|
|
369
388
|
structure: 'logicprobe/structure/v1',
|
|
370
389
|
granularity: 'logicprobe/granularity/v1',
|
|
371
390
|
baseline: 'logicprobe/baseline/v1',
|
|
@@ -1060,7 +1079,9 @@ function checkResult(id: string, name: string, findings: Finding[], detail: stri
|
|
|
1060
1079
|
return {
|
|
1061
1080
|
id,
|
|
1062
1081
|
name,
|
|
1063
|
-
|
|
1082
|
+
// An info finding is a note, not a defect: it must not flip a check to `fail`, or the
|
|
1083
|
+
// check would be dragged into the "resolve the failing checks" list for a remark.
|
|
1084
|
+
status: errors + warnings === 0 ? 'pass' : 'fail',
|
|
1064
1085
|
detail: detail + (errors > 0 ? ' (' + errors + ' errors' + (warnings > 0 ? ', ' + warnings + ' warnings' : '') + ')' : warnings > 0 ? ' (' + warnings + ' warnings)' : ''),
|
|
1065
1086
|
findings,
|
|
1066
1087
|
}
|
|
@@ -2893,7 +2914,7 @@ export function runVerification(input: unknown, options: VerificationOptions = {
|
|
|
2893
2914
|
...metadata,
|
|
2894
2915
|
summary: { states: 0, transitions: 0, errors: validation.errors.length, warnings: 0, checksRun: 0 },
|
|
2895
2916
|
checks: [modelCheck],
|
|
2896
|
-
nextSteps: verificationNextSteps([modelCheck], undefined),
|
|
2917
|
+
nextSteps: [...refusalGuidance(validation.errors), ...verificationNextSteps([modelCheck], undefined)],
|
|
2897
2918
|
}
|
|
2898
2919
|
}
|
|
2899
2920
|
const model = validation.model
|
|
@@ -2973,6 +2994,16 @@ export function runVerification(input: unknown, options: VerificationOptions = {
|
|
|
2973
2994
|
const warnings = checks.reduce((sum, check) => sum + check.findings.filter((finding) => finding.severity === 'warning').length, 0)
|
|
2974
2995
|
const coverageNotes = computeCoverageNotes(model)
|
|
2975
2996
|
const narrativeCoverage = narrativeCoverageOf(model)
|
|
2997
|
+
// R3: a partial narrative is valid, but the gap must be machine-readable instead of only a
|
|
2998
|
+
// sentence in `nextSteps`. It rides on S5 — the check that already reports undocumented
|
|
2999
|
+
// (state, event) combinations — so the documented check count stays 22.
|
|
3000
|
+
if (narrativeCoverage !== undefined && !narrativeComplete(narrativeCoverage)) {
|
|
3001
|
+
checks.find((check) => check.id === 'S5')?.findings.push({
|
|
3002
|
+
code: 'NARRATIVE_PARTIAL',
|
|
3003
|
+
severity: 'info',
|
|
3004
|
+
message: 'Narrative coverage is partial (states ' + narrativeCoverage.states + ', events ' + narrativeCoverage.events + ', scenarios ' + narrativeCoverage.scenarios + '): the checks still run over the whole model, but the undocumented symbols have to be re-derived by the reader.',
|
|
3005
|
+
})
|
|
3006
|
+
}
|
|
2976
3007
|
return {
|
|
2977
3008
|
ok: true,
|
|
2978
3009
|
ran: true,
|
|
@@ -3223,11 +3254,21 @@ export function runCompositionVerification(machinesInput: unknown[], options: Co
|
|
|
3223
3254
|
.map((machine) => 'machine ' + String(machine.index) + ' at ' + machine.state + ': ' + (machine.blocked.length === 0
|
|
3224
3255
|
? 'empty event alphabet'
|
|
3225
3256
|
: machine.blocked.map((entry) => entry.event + ' (' + entry.reason + ')').join(', ')))
|
|
3257
|
+
// A non-terminal state with no outgoing transition anywhere in its own model is a
|
|
3258
|
+
// modelling gap, not a design defect: without this note the census reads as
|
|
3259
|
+
// "the architecture deadlocks" when the model was simply left unfinished.
|
|
3260
|
+
const gaps = node.runtimes
|
|
3261
|
+
.map((runtime, index) => ({ index, state: runtime.state }))
|
|
3262
|
+
.filter((entry) => !isTerminal(models[entry.index], entry.state)
|
|
3263
|
+
&& !models[entry.index].transitions.some((transition) => transition.from === entry.state))
|
|
3264
|
+
const gapNote = gaps.length === 0
|
|
3265
|
+
? ''
|
|
3266
|
+
: ' Modelling note: ' + gaps.map((gap) => 'machine ' + String(gap.index) + ' state ' + gap.state).join(', ') + ' has no outgoing transition in its own model, so this deadlock may be a modelling gap rather than a design defect.'
|
|
3226
3267
|
c1Findings.push({
|
|
3227
3268
|
code: 'C1_COMPOSITION_DEADLOCK',
|
|
3228
3269
|
severity: 'error',
|
|
3229
3270
|
message: 'Composition deadlock: no machine can advance from (' + node.runtimes.map((runtime) => runtime.state).join(', ') + ') while at least one is not terminal.',
|
|
3230
|
-
detail: 'Shortest counterexample (breadth-first): ' + String(node.path.length) + ' step(s) to reach it. ' + reasons.join(' | '),
|
|
3271
|
+
detail: 'Shortest counterexample (breadth-first): ' + String(node.path.length) + ' step(s) to reach it. ' + reasons.join(' | ') + gapNote,
|
|
3231
3272
|
evidence: { steps: node.path, states: node.runtimes.map((runtime) => runtime.state), depth: node.path.length, perMachine, reasons },
|
|
3232
3273
|
})
|
|
3233
3274
|
continue
|
|
@@ -3269,7 +3310,7 @@ export function runCompositionVerification(machinesInput: unknown[], options: Co
|
|
|
3269
3310
|
const caveat = truncated ? ' The search was truncated at ' + String(maxStates) + ' composite states, so this may be an artefact of the cap rather than a property of the model.' : ''
|
|
3270
3311
|
c2Findings.push({
|
|
3271
3312
|
code: 'C2_RENDEZVOUS_NEVER_FIRES',
|
|
3272
|
-
severity: '
|
|
3313
|
+
severity: 'error',
|
|
3273
3314
|
message: 'Rendezvous event ' + event + ' can never fire: ' + reason + '.' + caveat,
|
|
3274
3315
|
detail: machines.map((machine) => 'machine ' + String(machine.index) + (machine.declares ? ' declares' : ' does not declare')
|
|
3275
3316
|
+ (machine.everEnabled ? ', enabled at ' + (machine.enabledAt.length === 0 ? 'no visited state' : machine.enabledAt.join('/')) : ', never enabled')).join(' | '),
|
|
@@ -3277,15 +3318,18 @@ export function runCompositionVerification(machinesInput: unknown[], options: Co
|
|
|
3277
3318
|
})
|
|
3278
3319
|
}
|
|
3279
3320
|
}
|
|
3280
|
-
|
|
3281
|
-
|
|
3321
|
+
// C2 is an error, not a warning: a rendezvous that can never fire means the composition
|
|
3322
|
+
// cannot advance through that handshake — the same "stuck forever" outcome C1 reports,
|
|
3323
|
+
// so a gate that only reads `verdict` must be able to stop on it.
|
|
3324
|
+
const errors = c1Findings.length + c2Findings.length
|
|
3325
|
+
const warnings = 0
|
|
3282
3326
|
const c1Detail = c1Findings.length > 0
|
|
3283
3327
|
? 'Composition deadlocks: ' + String(c1Findings.length)
|
|
3284
3328
|
: truncated
|
|
3285
3329
|
? 'No composition deadlock found, but the search was truncated at ' + String(maxStates) + ' composite states: absence is not proven'
|
|
3286
3330
|
: 'No composition deadlock reachable'
|
|
3287
3331
|
const c2Detail = c2Findings.length > 0
|
|
3288
|
-
? 'Rendezvous
|
|
3332
|
+
? 'Rendezvous errors: ' + String(c2Findings.length)
|
|
3289
3333
|
: truncated
|
|
3290
3334
|
? 'All rendezvous events fired, but the search was truncated at ' + String(maxStates) + ' composite states'
|
|
3291
3335
|
: 'All rendezvous events can fire'
|
|
@@ -3294,8 +3338,21 @@ export function runCompositionVerification(machinesInput: unknown[], options: Co
|
|
|
3294
3338
|
checkResult('C2', 'Rendezvous Sync', c2Findings, c2Detail),
|
|
3295
3339
|
]
|
|
3296
3340
|
const nextSteps: string[] = []
|
|
3297
|
-
if (errors > 0)
|
|
3298
|
-
|
|
3341
|
+
if (errors > 0) {
|
|
3342
|
+
const why = c1Findings.length > 0
|
|
3343
|
+
? 'the composition deadlocks at a reachable state, so at least one machine is missing an exit or a handshake partner'
|
|
3344
|
+
: 'a rendezvous event can never fire, so the composition cannot advance through that handshake'
|
|
3345
|
+
nextSteps.push('Resolve the error findings first: ' + why + '.')
|
|
3346
|
+
}
|
|
3347
|
+
// Composition couples machines only through rendezvous: each machine's `variables` are
|
|
3348
|
+
// private to it, so two machines cannot share one predicate. Name them when the input
|
|
3349
|
+
// looks like it is trying to (two or more machines each declare variables).
|
|
3350
|
+
const owners = models
|
|
3351
|
+
.map((entry, index) => ({ index, names: (entry.variables ?? []).map((variable) => variable.name) }))
|
|
3352
|
+
.filter((entry) => entry.names.length > 0)
|
|
3353
|
+
if (owners.length >= 2) {
|
|
3354
|
+
nextSteps.push('Each machine\'s variables are private to it (' + owners.map((entry) => 'machine ' + String(entry.index) + ': ' + entry.names.join(', ')).join('; ') + '): composition couples machines only through rendezvous, so a value two machines must agree on has to be modelled as a handshake, or inside a single machine.')
|
|
3355
|
+
}
|
|
3299
3356
|
nextSteps.push('Re-run the composition after the change; every machine must be able to advance, or explain why it is terminal.')
|
|
3300
3357
|
return {
|
|
3301
3358
|
ok: errors === 0,
|
package/src/export-tool.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
|
2
2
|
import type { JsonValue } from './json-value.js'
|
|
3
3
|
import { REPORT_SCHEMAS, DEFAULT_HASH_SPEC, modelHash, refusalVerdict, validateModel, verdictOf } from './engine.js'
|
|
4
4
|
import { exportModel, type ExportFormat } from './exporters.js'
|
|
5
|
+
import { renderReportText } from './report-view.js'
|
|
5
6
|
|
|
6
7
|
/** The model hash of an export's input, when the input validates (the success path always does). */
|
|
7
8
|
function exportHashes(model: unknown): { hashSpec: typeof DEFAULT_HASH_SPEC; modelHash?: string } {
|
|
@@ -34,14 +35,19 @@ export const logicProbeExportTool = defineTool({
|
|
|
34
35
|
enum: ['uppaal', 'tla', 'prism', 'spin'],
|
|
35
36
|
description: 'Target tool format.',
|
|
36
37
|
},
|
|
38
|
+
reportView: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
enum: ['digest', 'full'],
|
|
41
|
+
description: 'digest (default) prints the generated file verbatim; full prints the structured result as JSON.',
|
|
42
|
+
},
|
|
37
43
|
},
|
|
38
44
|
output: {
|
|
39
45
|
schema: {
|
|
40
46
|
type: 'json',
|
|
41
47
|
description: 'Export result with generated file content.',
|
|
42
48
|
},
|
|
43
|
-
render(
|
|
44
|
-
return [{ type: 'text' as const, text:
|
|
49
|
+
render(args, value) {
|
|
50
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
45
51
|
},
|
|
46
52
|
},
|
|
47
53
|
timeoutMs: 10_000,
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* logicprobe — DeepSeek Harness native plugin for the Logic Probe toolbox.
|
|
3
|
-
* Injects the session-start gate text (what this plugin checks, the
|
|
4
|
-
* the
|
|
3
|
+
* Injects the session-start gate text (what this plugin checks, the routing to
|
|
4
|
+
* the skills, the tool list, how to read a report)
|
|
5
5
|
* into the first model step of every agent session, mirroring the SessionStart
|
|
6
6
|
* hook the Claude Code plugin installs.
|
|
7
7
|
* The skill ships in this package's `skills/` directory and is registered at
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
* to the first step after their promotion, and the history guard re-injects it
|
|
19
19
|
* there. The default gate text is the dsh-native adaptation of
|
|
20
20
|
* `hooks/session-start-content.md`. Both are a guide to calling the tools: what
|
|
21
|
-
* this plugin checks, the
|
|
22
|
-
*
|
|
21
|
+
* this plugin checks, the routing to the sibling skills, the tool list, and how to
|
|
22
|
+
* read a report. The Red Flags table is enforcement scaffolding and lives in the
|
|
23
|
+
* skill body instead, where a session that needs it can load it.
|
|
23
24
|
* They stay in sync. Method, doctrine and per-domain detail live in the skills
|
|
24
25
|
* and their references, not here. Deployments override via Config.
|
|
25
26
|
*
|
|
@@ -75,41 +76,28 @@ const GATE_SOURCE_KIND: 'plugin:logicprobe' = 'plugin:logicprobe'
|
|
|
75
76
|
|
|
76
77
|
export type InteractionMode = 'ask' | 'auto' | 'follow-approval'
|
|
77
78
|
|
|
78
|
-
const DEFAULT_GATE_CONTENT = `
|
|
79
|
-
<EXTREMELY_IMPORTANT>
|
|
80
|
-
Plugin logicprobe is active. It checks claims about designs and code: facts against the source, behaviour as an executable model.
|
|
81
|
-
|
|
82
|
-
**
|
|
83
|
-
|
|
84
|
-
**
|
|
85
|
-
|
|
86
|
-
**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- \`
|
|
91
|
-
- \`
|
|
92
|
-
- \`
|
|
93
|
-
- \`
|
|
94
|
-
- \`
|
|
95
|
-
- \`
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
**Red Flags** — if you think any of these, stop:
|
|
102
|
-
|
|
103
|
-
| You think | Reality |
|
|
104
|
-
|-----------|---------|
|
|
105
|
-
| "Too simple to check" | The check is cheap. The silent skip is not. |
|
|
106
|
-
| "I read the code, so I know" | Knowing is not evidence. Cite \`file:line\`, or run the model, or call the claim unverified. |
|
|
107
|
-
| "The spec or the diagram says so" | Both are claims about the code. Reconcile them with a source-side scan. |
|
|
108
|
-
| "I'll check while implementing" | Check before the decision, not after. |
|
|
109
|
-
| "Reasoning is enough" | Reasoning is not a run. One counter-example kills a universal claim. |
|
|
110
|
-
| "The report passed, so the code is fine" | The report covers the model you gave it. Reconcile it, and say what you did not check. |
|
|
111
|
-
|
|
112
|
-
</EXTREMELY_IMPORTANT>
|
|
79
|
+
const DEFAULT_GATE_CONTENT = `
|
|
80
|
+
<EXTREMELY_IMPORTANT>
|
|
81
|
+
Plugin logicprobe is active. It checks claims about designs and code: facts against the source, behaviour as an executable model.
|
|
82
|
+
|
|
83
|
+
**Unsure whether a check applies?** Load \`logicprobe\` first. It is the entry point: it owns the doctrine, the model schema, the depth rules and the routing to its siblings.
|
|
84
|
+
|
|
85
|
+
**Load the skill that matches the claim**: \`logicprobe\` (entry point; claim and behaviour checks), \`logicprobe-uml\` (diagrams), \`logicprobe-structure\` (architecture, dependencies), \`logicprobe-concurrency\` (concurrency claims), \`logicprobe-datamodel\` (data models, migrations).
|
|
86
|
+
|
|
87
|
+
**Tools** (dsh):
|
|
88
|
+
|
|
89
|
+
- \`logicprobe_verify\` — state machines: structural checks, adversarial probes, budgets, deadlines, before/after regression.
|
|
90
|
+
- \`logicprobe_datamodel_verify\` — data models and migrations: structure, migration coverage, copy consistency, rollback, before/after data.
|
|
91
|
+
- \`logicprobe_compose_verify\` — two or more machines: deadlock, and a rendezvous that never fires.
|
|
92
|
+
- \`logicprobe_concurrency_scan\` — finds concurrency claims and routes them. It proves nothing about concurrency.
|
|
93
|
+
- \`logicprobe_export\` — UPPAAL, TLA+, PRISM and SPIN input from a verified model.
|
|
94
|
+
- \`logicprobe_uml\` — render, parse or review a UML diagram.
|
|
95
|
+
- \`logicprobe_structure_verify\` — dependency review of a structure diagram, at one level or several.
|
|
96
|
+
- \`logicprobe_report_diff\` — baseline against current: what a change added, removed or changed.
|
|
97
|
+
|
|
98
|
+
**Read a report**: gate on \`verdict\` (\`pass\`, \`pass_with_findings\`, \`fail\`), never on \`ok\`. Reports carry \`verdictReason\`, \`hashes\` and \`nextSteps\`. Keys starting with \`_\` are notes: ignored, and left out of the model hash. A tool prints the report body: passing checks stay in the inventory line, repeated facts are grouped, and warning evidence is omitted — pass \`reportView: "full"\` only when you need the structured report itself, which \`logicprobe_report_diff\` consumes.
|
|
99
|
+
|
|
100
|
+
</EXTREMELY_IMPORTANT>
|
|
113
101
|
`
|
|
114
102
|
|
|
115
103
|
/** A schemastery field that may or may not carry `.volatile()`. */
|
|
@@ -261,7 +249,7 @@ function inspectProvider(config: Config, isToolRegistered: () => boolean, isData
|
|
|
261
249
|
return {
|
|
262
250
|
manifest: {
|
|
263
251
|
id: 'logicprobe',
|
|
264
|
-
description: 'Session-start gate injection and native verification tooling for the Logic Probe toolbox. The gate is a guide to calling the tools: what the plugin checks, the
|
|
252
|
+
description: 'Session-start gate injection and native verification tooling for the Logic Probe toolbox. The gate is a guide to calling the tools: what the plugin checks, the routing to the sibling skills, the tool list, and how to read a report. Doctrine and per-domain detail live in the skills.',
|
|
265
253
|
methods: [
|
|
266
254
|
{
|
|
267
255
|
name: 'status',
|
package/src/report-diff-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 { diffReports } from './baseline.js'
|
|
4
|
+
import { renderReportText } from './report-view.js'
|
|
4
5
|
|
|
5
6
|
export const LOGICPROBE_REPORT_DIFF_TOOL_NAME = 'logicprobe_report_diff'
|
|
6
7
|
|
|
@@ -25,14 +26,19 @@ export const logicProbeReportDiffTool = defineTool({
|
|
|
25
26
|
required: true,
|
|
26
27
|
description: 'The current report to compare against the baseline.',
|
|
27
28
|
},
|
|
29
|
+
reportView: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
enum: ['digest', 'full'],
|
|
32
|
+
description: 'digest (default) prints the readable body; full prints the structured diff report.',
|
|
33
|
+
},
|
|
28
34
|
},
|
|
29
35
|
output: {
|
|
30
36
|
schema: {
|
|
31
37
|
type: 'json',
|
|
32
38
|
description: 'Baseline diff report: added/removed/changed findings, delta verdict, current verdict, summary and next steps.',
|
|
33
39
|
},
|
|
34
|
-
render(
|
|
35
|
-
return [{ type: 'text' as const, text:
|
|
40
|
+
render(args, value) {
|
|
41
|
+
return [{ type: 'text' as const, text: renderReportText(value, { full: args.reportView === 'full' }) }]
|
|
36
42
|
},
|
|
37
43
|
},
|
|
38
44
|
timeoutMs: 10_000,
|