dsh-logicprobe 0.9.0 → 1.0.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 (63) hide show
  1. package/README.en-US.md +79 -14
  2. package/README.md +79 -14
  3. package/lib/baseline.js +142 -0
  4. package/lib/client.js +56 -1
  5. package/lib/compose-tool.js +1 -1
  6. package/lib/concurrency.js +14 -0
  7. package/lib/data-engine.js +42 -8
  8. package/lib/engine.js +379 -79
  9. package/lib/export-tool.js +27 -2
  10. package/lib/granularity.js +237 -0
  11. package/lib/index.js +63 -41
  12. package/lib/report-diff-tool.js +39 -0
  13. package/lib/structure-tool.js +64 -0
  14. package/lib/structure.js +648 -0
  15. package/lib/tool.js +3 -3
  16. package/lib/types/baseline.d.ts +69 -0
  17. package/lib/types/concurrency.d.ts +10 -0
  18. package/lib/types/data-engine.d.ts +13 -1
  19. package/lib/types/engine.d.ts +138 -1
  20. package/lib/types/granularity.d.ts +106 -0
  21. package/lib/types/index.d.ts +9 -8
  22. package/lib/types/report-diff-tool.d.ts +8 -0
  23. package/lib/types/structure-tool.d.ts +10 -0
  24. package/lib/types/structure.d.ts +156 -0
  25. package/lib/types/tool.d.ts +2 -2
  26. package/lib/types/uml.d.ts +89 -0
  27. package/lib/uml-tool.js +36 -7
  28. package/lib/uml.js +217 -23
  29. package/locale/en.json +6 -0
  30. package/locale/zh.json +6 -0
  31. package/package.json +16 -14
  32. package/skills/logicprobe/SKILL.md +84 -58
  33. package/skills/logicprobe/references/dsh-model-schema.md +81 -10
  34. package/skills/logicprobe/references/gap-routing-guide.md +37 -36
  35. package/skills/logicprobe/references/hash-spec.md +128 -0
  36. package/skills/logicprobe/references/logic-verification-guide.md +27 -250
  37. package/skills/logicprobe-concurrency/SKILL.md +43 -0
  38. package/skills/{logicprobe → logicprobe-concurrency}/references/concurrency-risk-guide.md +1 -1
  39. package/skills/logicprobe-datamodel/SKILL.md +5 -4
  40. package/skills/logicprobe-datamodel/references/data-model-guide.md +1 -1
  41. package/skills/logicprobe-datamodel/references/data-model-schema.md +1 -1
  42. package/skills/logicprobe-structure/SKILL.md +95 -0
  43. package/skills/logicprobe-structure/references/structure-review-guide.md +177 -0
  44. package/skills/logicprobe-uml/SKILL.md +55 -0
  45. package/skills/{logicprobe → logicprobe-uml}/references/uml-modeling-guide.md +120 -8
  46. package/src/baseline.ts +180 -0
  47. package/src/client.js +56 -1
  48. package/src/compose-tool.ts +47 -47
  49. package/src/concurrency.ts +23 -0
  50. package/src/data-engine.ts +51 -9
  51. package/src/engine.ts +441 -67
  52. package/src/export-tool.ts +73 -48
  53. package/src/granularity.ts +312 -0
  54. package/src/index.ts +63 -41
  55. package/src/report-diff-tool.ts +43 -0
  56. package/src/structure-tool.ts +68 -0
  57. package/src/structure.ts +724 -0
  58. package/src/tool.ts +3 -3
  59. package/src/uml-tool.ts +38 -8
  60. package/src/uml.ts +275 -24
  61. package/skills/logicprobe/references/logicprobe-engine.py +0 -4943
  62. package/skills/logicprobe/references/verification-harness.py +0 -959
  63. package/skills/logicprobe-datamodel/references/data-model-harness.py +0 -528
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Report diffing: "did this slice add findings, or remove them?"
3
+ *
4
+ * The acceptance criterion of a refactoring slice is usually *not* "zero findings" —
5
+ * it is "no NEW findings, and none of the old ones got worse". Comparing two JSON
6
+ * reports by hand is how that gets fudged, so the comparison is code.
7
+ *
8
+ * A finding's identity is its machine-readable locator, never its prose:
9
+ * `check|code|<canonical evidence + path>`. Findings that carry neither evidence nor a
10
+ * path fall back to their message, because an engine that reports nothing structured
11
+ * has nothing stabler to match on. Two runs over the same input must produce zero
12
+ * added and zero removed findings.
13
+ *
14
+ * @module logicprobe-baseline
15
+ */
16
+ import { type Finding, type Verdict, type VerdictSummary } from './engine.js';
17
+ /** A finding as it appears in any report family, after flattening. */
18
+ export interface ReportFinding extends Finding {
19
+ /** The check that emitted it (verify/datamodel: `S2`, `C1`, …); absent for flat families. */
20
+ check?: string;
21
+ }
22
+ export interface BaselineChange {
23
+ identity: string;
24
+ code: string;
25
+ before: {
26
+ severity: string;
27
+ message: string;
28
+ };
29
+ after: {
30
+ severity: string;
31
+ message: string;
32
+ };
33
+ }
34
+ export interface BaselineReport {
35
+ ok: boolean;
36
+ ran: boolean;
37
+ verdict: Verdict;
38
+ verdictReason: string;
39
+ /** The versioned report contract this result follows. */
40
+ schema: string;
41
+ /** The current report's own verdict, echoed so "no new findings" cannot hide a failing run. */
42
+ currentVerdict: Verdict;
43
+ added: ReportFinding[];
44
+ removed: ReportFinding[];
45
+ changed: BaselineChange[];
46
+ summary: {
47
+ baseline: number;
48
+ current: number;
49
+ added: number;
50
+ removed: number;
51
+ changed: number;
52
+ addedErrors: number;
53
+ addedWarnings: number;
54
+ };
55
+ nextSteps: string[];
56
+ }
57
+ /**
58
+ * The stable identity of a finding: check + file + code + a canonical locator. Prose is
59
+ * excluded so a reworded message reads as `changed`, not as a new finding.
60
+ */
61
+ export declare function findingIdentity(finding: ReportFinding): string;
62
+ /** Flatten a report of any family into one finding list (`checks[].findings` or `findings[]`). */
63
+ export declare function flattenReportFindings(report: unknown): ReportFinding[];
64
+ /**
65
+ * Compare a baseline report with the current one. The verdict describes the *delta*
66
+ * (a new error fails it), while `currentVerdict` keeps the absolute result visible.
67
+ */
68
+ export declare function diffReports(baseline: unknown, current: unknown): BaselineReport;
69
+ export type { VerdictSummary };
@@ -1,3 +1,4 @@
1
+ import { type ReportSchema, type Verdict } from './engine.js';
1
2
  export interface ConcurrencyFinding {
2
3
  code: 'CONCURRENCY_KEYWORD' | 'CONCURRENCY_ABSOLUTE_CLAIM';
3
4
  severity: 'warning' | 'error';
@@ -10,6 +11,11 @@ export interface ConcurrencyFinding {
10
11
  }
11
12
  export interface ConcurrencyScanReport {
12
13
  ok: boolean;
14
+ ran: boolean;
15
+ verdict: Verdict;
16
+ verdictReason: string;
17
+ /** The versioned report contract this result follows. */
18
+ schema: ReportSchema;
13
19
  findings: ConcurrencyFinding[];
14
20
  summary: {
15
21
  lines: number;
@@ -18,5 +24,9 @@ export interface ConcurrencyScanReport {
18
24
  warnings: number;
19
25
  errors: number;
20
26
  };
27
+ /** This scan hashes no model; the field is present so every report has the same shape. */
28
+ hashes: Record<string, string>;
29
+ /** What to do next, derived from the findings — never empty. */
30
+ nextSteps: string[];
21
31
  }
22
32
  export declare function runConcurrencyScan(text: string): ConcurrencyScanReport;
@@ -1,4 +1,4 @@
1
- import type { CheckResult } from './engine.js';
1
+ import { type CheckResult, type ReportSchema, type Verdict } from './engine.js';
2
2
  export declare const DATA_ENGINE_SCHEMA_VERSION = 1;
3
3
  export type DataValue = number | string | boolean | null;
4
4
  export type DataType = 'string' | 'integer' | 'number' | 'boolean' | 'uuid' | 'date' | 'datetime' | 'timestamp' | 'json' | 'enum' | 'array' | 'object' | 'binary';
@@ -174,9 +174,19 @@ export interface DataComparisonSummary {
174
174
  removedRelationships: string[];
175
175
  }
176
176
  export interface DataVerificationReport {
177
+ /** The engine ran and the input was well-formed enough to produce this report. Read `verdict` for the review outcome. */
177
178
  ok: boolean;
179
+ ran: boolean;
180
+ verdict: Verdict;
181
+ verdictReason: string;
182
+ /** The versioned report contract this result follows. */
183
+ schema: ReportSchema;
178
184
  schemaVersion: 1;
179
185
  modelHash: string;
186
+ /** Every hash this report carries, in one place. The data-model hash follows the same normalization as the published spec, but is not covered by it. */
187
+ hashes: {
188
+ modelHash: string;
189
+ };
180
190
  summary: {
181
191
  entities: number;
182
192
  fields: number;
@@ -187,6 +197,8 @@ export interface DataVerificationReport {
187
197
  };
188
198
  checks: CheckResult[];
189
199
  comparison?: DataComparisonSummary;
200
+ /** What to do next, derived from the findings — never empty. */
201
+ nextSteps: string[];
190
202
  }
191
203
  export declare function dataModelHash(model: DataModelV1): string;
192
204
  export declare function validateDataModel(input: unknown): {
@@ -167,6 +167,8 @@ export interface VerificationOptions {
167
167
  maxPermutationEvents?: number;
168
168
  beforeModel?: unknown;
169
169
  stateMapping?: Record<string, string>;
170
+ /** Hash specification used for modelHash. Defaults to the current published spec (`v1`). */
171
+ hashSpec?: HashSpec;
170
172
  }
171
173
  export interface PathStep {
172
174
  from: string;
@@ -177,6 +179,12 @@ export interface Finding {
177
179
  code: string;
178
180
  severity: 'error' | 'warning';
179
181
  message: string;
182
+ /** Longer explanation: the shortest-path note, the per-machine reasons, the quoted source lines. */
183
+ detail?: string;
184
+ /** The artefact the finding is about (a diagram name, a file path) — set when one report covers several. */
185
+ file?: string;
186
+ /** 1-based line in that artefact, when the check knows it. */
187
+ line?: number;
180
188
  path?: PathStep[];
181
189
  evidence?: Record<string, unknown>;
182
190
  }
@@ -188,9 +196,34 @@ export interface CheckResult {
188
196
  findings: Finding[];
189
197
  }
190
198
  export interface VerificationReport {
199
+ /**
200
+ * The engine ran and the input was well-formed enough to produce this report.
201
+ * It does NOT mean the review passed — read `verdict` for that.
202
+ */
191
203
  ok: boolean;
204
+ /** The tool executed and produced a report. False only when a tool refused the request before the engine ran. */
205
+ ran: boolean;
206
+ /** The review outcome. `ok: true` with `verdict: "fail"` is a failing review, not a passing one. */
207
+ verdict: Verdict;
208
+ /** Why the verdict came out that way, e.g. `2 error finding(s) (first: S2_NO_TRANSITIONS)`. */
209
+ verdictReason: string;
210
+ /** The versioned report contract this result follows. */
211
+ schema: ReportSchema;
192
212
  schemaVersion: 1;
213
+ /** Which published specification `modelHash` follows (see references/hash-spec.md). */
214
+ hashSpec: HashSpec;
193
215
  modelHash: string;
216
+ /** Every hash this report carries, in one place, for a baseline diff or an archive record. */
217
+ hashes: {
218
+ hashSpec: HashSpec;
219
+ modelHash: string;
220
+ beforeModelHash?: string;
221
+ afterModelHash?: string;
222
+ };
223
+ /** Paths of the `_`-prefixed metadata keys found in the input and ignored by the schema. */
224
+ metadataKeys?: string[];
225
+ /** How much of the model the narrative documents; absent when there is no narrative. */
226
+ narrativeCoverage?: NarrativeCoverage;
194
227
  /** Echo of the model's natural-language narrative, when present. */
195
228
  narrative?: ModelNarrative;
196
229
  summary: {
@@ -203,6 +236,8 @@ export interface VerificationReport {
203
236
  };
204
237
  checks: CheckResult[];
205
238
  comparison?: ComparisonSummary;
239
+ /** What to do next, derived from the findings — never empty. */
240
+ nextSteps: string[];
206
241
  /** Informational notes about semantic dimensions this model references (timing, preemption)
207
242
  * that this engine does not verify. Heuristic, vocabulary-based — never a substitute for the checks. */
208
243
  coverageNotes?: string[];
@@ -226,7 +261,92 @@ export interface RuntimeState {
226
261
  state: string;
227
262
  vars: Record<string, VarValue>;
228
263
  }
229
- export declare function modelHash(model: LogicModelV1): string;
264
+ export declare function modelHash(model: LogicModelV1, spec?: HashSpec): string;
265
+ /**
266
+ * Deterministic JSON (sorted keys, no insignificant whitespace) — the serialization the
267
+ * hash specification is defined over. Exported so every report hashes inputs the same way.
268
+ */
269
+ export declare function canonicalJson(value: unknown): string;
270
+ /**
271
+ * Published model-hash specifications. The full normalization rules, the list of
272
+ * excluded keys and a one-line recompute command are in `references/hash-spec.md`.
273
+ *
274
+ * - `v1` (1.0.0) drops every `_`-prefixed metadata key at every level before hashing,
275
+ * so archiving `_source` / `_verified` next to a model cannot change its hash.
276
+ * - `v0` is the pre-1.0.0 behaviour (no metadata filtering), kept so an archived hash
277
+ * can still be checked against the old engine.
278
+ *
279
+ * For a model that carries no `_` keys the two specs are byte-identical.
280
+ */
281
+ export type HashSpec = 'v0' | 'v1';
282
+ export declare const PUBLISHED_HASH_SPECS: readonly HashSpec[];
283
+ export declare const DEFAULT_HASH_SPEC: HashSpec;
284
+ /**
285
+ * Versioned report contracts. Every tool result carries `schema`, so a consumer can
286
+ * branch on the contract instead of sniffing fields, and a change to a report's shape
287
+ * is a version bump here rather than a silent break. `findings[]` keeps the same
288
+ * stable core in every family: `{code, severity, message, detail?, evidence?, path?}`.
289
+ */
290
+ export declare const REPORT_SCHEMAS: {
291
+ readonly verify: 'logicprobe/verify/v1';
292
+ readonly compose: 'logicprobe/compose/v1';
293
+ readonly datamodel: 'logicprobe/datamodel/v1';
294
+ readonly concurrency: 'logicprobe/concurrency/v1';
295
+ readonly export: 'logicprobe/export/v1';
296
+ readonly umlRender: 'logicprobe/uml/render/v1';
297
+ readonly umlParse: 'logicprobe/uml/parse/v1';
298
+ readonly umlReview: 'logicprobe/uml/review/v1';
299
+ readonly structure: 'logicprobe/structure/v1';
300
+ readonly granularity: 'logicprobe/granularity/v1';
301
+ readonly baseline: 'logicprobe/baseline/v1';
302
+ };
303
+ export type ReportSchema = typeof REPORT_SCHEMAS[keyof typeof REPORT_SCHEMAS];
304
+ /**
305
+ * How much of the model the `narrative` block actually documents, as `covered/total`
306
+ * per dimension. A partial narrative is valid — writing the states first and the
307
+ * events later is the natural order — so this is a coverage report, not a gate.
308
+ * `scenarios` counts distinct modelled (from, event) groups: one scenario per group.
309
+ */
310
+ export interface NarrativeCoverage {
311
+ states: string;
312
+ events: string;
313
+ scenarios: string;
314
+ }
315
+ export declare function narrativeCoverageOf(model: LogicModelV1): NarrativeCoverage | undefined;
316
+ /** True when every dimension of the coverage is fully described. */
317
+ export declare function narrativeComplete(coverage: NarrativeCoverage | undefined): boolean;
318
+ /**
319
+ * The review outcome, kept separate from `ok` (= "the tool ran"). Collapsing the two
320
+ * is how a failed review gets read as a passing one.
321
+ */
322
+ export type Verdict = 'pass' | 'pass_with_findings' | 'fail';
323
+ export interface VerdictSummary {
324
+ verdict: Verdict;
325
+ verdictReason: string;
326
+ }
327
+ /** A tool that refused the request before running anything: it neither passed nor failed a review. */
328
+ export declare function refusalVerdict(reason: string): VerdictSummary;
329
+ /**
330
+ * Decide the verdict from finding counts. Any error-severity finding fails the review
331
+ * even though the tool ran successfully — that gap is exactly what `verdict` closes.
332
+ */
333
+ export declare function verdictOf(errors: number, warnings: number, firstCode?: string): VerdictSummary;
334
+ export declare function verdictOfFindings(findings: Array<{
335
+ code: string;
336
+ severity: string;
337
+ }>): VerdictSummary;
338
+ /**
339
+ * Paths of every `_`-prefixed key in the input, sorted (`_source`, `_verified`,
340
+ * `states[0]._note`; a metadata key's own children are not listed, since the subtree is
341
+ * metadata as a whole). The schema ignores these keys as annotation metadata; echoing
342
+ * them in the report makes the ignored metadata auditable instead of invisible.
343
+ */
344
+ export declare function metadataKeysOf(input: unknown): string[];
345
+ /**
346
+ * The exact payload a hash spec hashes. Exported so the specification has one
347
+ * implementation and the documentation can be checked against it.
348
+ */
349
+ export declare function hashPayload(model: unknown, spec?: HashSpec): unknown;
230
350
  export declare function validateModel(input: unknown): {
231
351
  ok: true;
232
352
  model: LogicModelV1;
@@ -245,6 +365,8 @@ export interface CompositionOptions {
245
365
  /** Events that require a synchronized multi-machine step (handshake). */
246
366
  rendezvous?: string[];
247
367
  maxStates?: number;
368
+ /** Hash specification used for the per-machine modelHash. Defaults to the current published spec (`v1`). */
369
+ hashSpec?: HashSpec;
248
370
  }
249
371
  export interface CompositionSummary {
250
372
  machineCount: number;
@@ -259,9 +381,24 @@ export interface CompositionSummary {
259
381
  truncated: boolean;
260
382
  }
261
383
  export interface CompositionReport {
384
+ /** Historical meaning: the composition has no error-severity finding. Read `verdict` for the review outcome. */
262
385
  ok: boolean;
386
+ ran: boolean;
387
+ verdict: Verdict;
388
+ verdictReason: string;
389
+ /** The versioned report contract this result follows. */
390
+ schema: ReportSchema;
391
+ /** Which published specification the per-machine `modelHash` values follow. */
392
+ hashSpec: HashSpec;
393
+ /** Every hash this report carries, in one place. */
394
+ hashes: {
395
+ hashSpec: HashSpec;
396
+ machines: string[];
397
+ };
263
398
  summary: CompositionSummary;
264
399
  checks: CheckResult[];
400
+ /** What to do next, derived from the findings — never empty. */
401
+ nextSteps: string[];
265
402
  }
266
403
  /**
267
404
  * N-machine composition semantics (documented in dsh-model-schema.md):
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Multi-granularity structure review: one architecture, several levels of detail.
3
+ *
4
+ * A module diagram and the repository-wide diagram it belongs to are two views of one
5
+ * fact, and nothing keeps them in step — the child grows a dependency the parent never
6
+ * had, or the parent keeps an arrow the child quietly stopped drawing. This module
7
+ * checks that a child diagram is a **refinement** of its declared parent:
8
+ *
9
+ * - every dependency the child draws between two nodes the parent also has must exist in
10
+ * the parent (nothing appears out of nowhere);
11
+ * - an edge the parent draws and the child does not must be *expanded* in the child, i.e.
12
+ * a child path must connect the same two ends;
13
+ * - the declared parent relation must be a forest, not a cycle.
14
+ *
15
+ * Every per-diagram structural check still runs, and its findings are tagged with the
16
+ * diagram they came from (`file`), so one report can carry several diagrams without the
17
+ * findings becoming anonymous.
18
+ *
19
+ * @module logicprobe-granularity
20
+ */
21
+ import { type Finding, type ReportSchema, type Verdict, type VerdictSummary } from './engine.js';
22
+ import { type StructureNotation } from './structure.js';
23
+ export interface GranularityDiagram {
24
+ /** Name of this level, e.g. `L2/motion` or `repo`. Referenced by a child's `parent`. */
25
+ name: string;
26
+ /** Diagram source text. */
27
+ diagram: string;
28
+ /** Name of the diagram this one refines; absent for a root. */
29
+ parent?: string;
30
+ }
31
+ export interface GranularityOptions {
32
+ diagrams: GranularityDiagram[];
33
+ notation?: 'auto' | StructureNotation;
34
+ /** Optional dependency matrix, applied to every diagram. */
35
+ matrix?: unknown;
36
+ }
37
+ export interface GranularityEdgeRef {
38
+ from: string;
39
+ to: string;
40
+ line?: number;
41
+ }
42
+ export interface GranularityPairSummary {
43
+ parent: string;
44
+ child: string;
45
+ parentNodes: number;
46
+ childNodes: number;
47
+ parentEdges: number;
48
+ childEdges: number;
49
+ /** Child edges whose endpoints both exist in the parent and which the parent also draws. */
50
+ inheritedEdges: number;
51
+ /** Child edges with at least one endpoint the parent does not have (the refinement's own detail). */
52
+ newEdges: number;
53
+ /** Parent edges with no direct child counterpart, but a child path connects the same ends. */
54
+ expandedEdges: GranularityEdgeRef[];
55
+ /** Parent edges with no direct counterpart and no connecting path — the refinement lost them. */
56
+ missingEdges: GranularityEdgeRef[];
57
+ /** Child edges between two parent nodes that the parent does not draw — invented dependencies. */
58
+ inventedEdges: GranularityEdgeRef[];
59
+ }
60
+ export interface GranularityDiagramSummary {
61
+ name: string;
62
+ parent?: string;
63
+ notation: StructureNotation;
64
+ nodes: number;
65
+ edges: number;
66
+ verdict: Verdict;
67
+ findings: number;
68
+ }
69
+ export interface GranularityReport {
70
+ ok: boolean;
71
+ ran: boolean;
72
+ verdict: Verdict;
73
+ verdictReason: string;
74
+ schema: ReportSchema;
75
+ diagrams: GranularityDiagramSummary[];
76
+ pairs: GranularityPairSummary[];
77
+ findings: Finding[];
78
+ summary: {
79
+ diagrams: number;
80
+ roots: number;
81
+ pairs: number;
82
+ nodes: number;
83
+ edges: number;
84
+ inventedEdges: number;
85
+ missingEdges: number;
86
+ expandedEdges: number;
87
+ errors: number;
88
+ warnings: number;
89
+ };
90
+ hashes: {
91
+ diagrams: Array<{
92
+ name: string;
93
+ hash: string;
94
+ }>;
95
+ };
96
+ warnings: string[];
97
+ nextSteps: string[];
98
+ }
99
+ /**
100
+ * Review a set of structure diagrams with declared parent relations. Per-diagram
101
+ * structural checks run first; the cross-diagram refinement checks follow.
102
+ */
103
+ export declare function reviewGranularity(options: GranularityOptions): GranularityReport;
104
+ /** Deterministic JSON of a granularity report's pair table, for a stable summary line. */
105
+ export declare function granularityDigest(pairs: GranularityPairSummary[]): string;
106
+ export type { VerdictSummary };
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * logicprobe — DeepSeek Harness native plugin for the Logic Probe toolbox.
3
- * Injects the session-start gate text (claim-verification doctrine, 1% Rule,
4
- * Red Flags, proactive suggestion) into the first model step of every agent
5
- * session, mirroring the SessionStart hook the Claude Code plugin installs.
3
+ * Injects the session-start gate text (what this plugin checks, the 1% Rule,
4
+ * the Red Flags, the routing to the skills, the tool list, how to read a report)
5
+ * into the first model step of every agent session, mirroring the SessionStart
6
+ * hook the Claude Code plugin installs.
6
7
  * The skill ships in this package's `skills/` directory and is registered at
7
8
  * apply time into dsh's `ctx.skills` registry through the standard filesystem
8
9
  * provider, so it appears in every session catalog without a manual copy step.
@@ -16,11 +17,11 @@
16
17
  * reminders (skill catalog, AGENTS.md, gate plugins) simply defer this message
17
18
  * to the first step after their promotion, and the history guard re-injects it
18
19
  * there. The default gate text is the dsh-native adaptation of
19
- * `hooks/session-start-content.md`: behavior rules
20
- * (1% Rule / Red Flags / proactive suggestion) stay in sync, while
21
- * presentation is adapted to dsh's native skill catalog — the trigger list
22
- * lives in the skill description, not duplicated in the gate. Deployments
23
- * override via Config.
20
+ * `hooks/session-start-content.md`. Both are a guide to calling the tools: what
21
+ * this plugin checks, the 1% Rule and the Red Flags that raise the chance of a
22
+ * call, the routing to the five skills, the tool list, and how to read a report.
23
+ * They stay in sync. Method, doctrine and per-domain detail live in the skills
24
+ * and their references, not here. Deployments override via Config.
24
25
  *
25
26
  * @module logicprobe-dsh
26
27
  */
@@ -0,0 +1,8 @@
1
+ export declare const LOGICPROBE_REPORT_DIFF_TOOL_NAME = "logicprobe_report_diff";
2
+ /**
3
+ * Model-visible DSH tool that compares two reports of the same family — the accepted
4
+ * "violations must not increase" acceptance criterion, computed instead of eyeballed.
5
+ * A finding's identity is its check + code + machine-readable locator, so a reworded
6
+ * message reads as `changed`, and two runs over the same input add nothing.
7
+ */
8
+ export declare const logicProbeReportDiffTool: import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,10 @@
1
+ export declare const LOGICPROBE_STRUCTURE_TOOL_NAME = "logicprobe_structure_verify";
2
+ /**
3
+ * Model-visible DSH tool that audits a *structure* diagram — components, packages,
4
+ * classes, deployment nodes and the dependency arrows between them — against an
5
+ * optional dependency matrix. A structure diagram is not a state machine: this tool
6
+ * reads the graph the diagram actually draws, instead of refusing it (which is what
7
+ * `logicprobe_uml action=parse` does) or pretending the arrows are state transitions
8
+ * (which is what a false `ok` used to hide).
9
+ */
10
+ export declare const logicProbeStructureTool: import("@deepseek-ai/dsh-tools").ToolDefinition;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Structure-diagram review: a dependency graph is not a state machine.
3
+ *
4
+ * The state-machine front end (`uml.ts`) reads a component, package, class or
5
+ * deployment diagram as a *by-product* and says so (`UML_NOT_A_STATE_DIAGRAM`).
6
+ * This module reads the same text as what it actually is — a directed graph of
7
+ * nodes and dependency edges — and audits it against a declared dependency matrix.
8
+ *
9
+ * The checks are the structural ones a reader performs by eye and gets wrong:
10
+ * an isolated node nobody notices, an arrow whose endpoint was never declared, a
11
+ * dependency cycle, an edge that violates the allowed-dependency matrix, a child
12
+ * depending on its parent, and an edge the matrix says must exist but the diagram
13
+ * does not draw. Every finding names the rule id it hit, so the diagram and the
14
+ * source-side checker can be reconciled rule by rule.
15
+ *
16
+ * @module logicprobe-structure
17
+ */
18
+ import { type Finding, type ReportSchema, type Verdict, type VerdictSummary } from './engine.js';
19
+ export type StructureNotation = 'plantuml' | 'mermaid';
20
+ /** What a node declares itself to be. Containers hold other nodes; they are not dependencies. */
21
+ export type StructureNodeKind = 'component' | 'class' | 'interface' | 'actor' | 'database' | 'node' | 'package' | 'rectangle' | 'cloud' | 'queue' | 'unknown';
22
+ export interface StructureNode {
23
+ id: string;
24
+ /** Display label when the declaration carried one (`component "Order Service" as SVC`). */
25
+ label?: string;
26
+ kind: StructureNodeKind;
27
+ /** True when the node is a container (package/rectangle/frame): it owns members and carries no dependency of its own. */
28
+ container: boolean;
29
+ /** 1-based line of the declaration. Absent when the node was only ever named by an edge. */
30
+ line?: number;
31
+ declared: boolean;
32
+ /** Container id this node was declared inside, if any. */
33
+ parent?: string;
34
+ }
35
+ export interface StructureEdge {
36
+ from: string;
37
+ to: string;
38
+ /** Arrow label, e.g. `read` in `PAY --> DRV : read`. */
39
+ label?: string;
40
+ line: number;
41
+ }
42
+ export interface StructureGraph {
43
+ notation: StructureNotation;
44
+ nodes: StructureNode[];
45
+ edges: StructureEdge[];
46
+ warnings: string[];
47
+ }
48
+ /** One dependency rule from the matrix document. */
49
+ export interface StructureRule {
50
+ id: string;
51
+ /** Node id or glob the rule applies to; absent = applies to every source. */
52
+ source?: string;
53
+ /** Permitted targets (glob or node id). Winning an allow match permits the edge. */
54
+ allow?: string[];
55
+ /** Forbidden targets. Deny always wins over allow. */
56
+ deny?: string[];
57
+ /** When true, at least one edge matching source→targets must exist (UML025). */
58
+ require?: boolean;
59
+ }
60
+ export interface StructureLayer {
61
+ name: string;
62
+ /** Node ids or globs that belong to this layer. */
63
+ members: string[];
64
+ }
65
+ /**
66
+ * The dependency matrix: the single source of truth a diagram is checked against.
67
+ * `default` decides the fate of an edge no rule mentions (`allow` = only explicit
68
+ * rules judge, `deny` = anything unlisted is a violation).
69
+ */
70
+ export interface DependencyMatrix {
71
+ rules?: StructureRule[];
72
+ layers?: StructureLayer[];
73
+ default?: 'allow' | 'deny';
74
+ }
75
+ export interface StructureEdgeVerdict {
76
+ from: string;
77
+ to: string;
78
+ line: number;
79
+ /** Rule ids whose `source` matched this edge, whether they allowed or denied it. */
80
+ matchedRules: string[];
81
+ allowed: boolean;
82
+ /** `allow` (a rule permitted it), `default` (nothing judged it), `deny` (a rule forbade it), `unlisted` (default=deny and no rule matched). */
83
+ basis: 'allow' | 'default' | 'deny' | 'unlisted';
84
+ }
85
+ export interface StructureSummary {
86
+ nodes: number;
87
+ edges: number;
88
+ containers: number;
89
+ isolated: number;
90
+ dangling: number;
91
+ cycles: number;
92
+ disallowedEdges: number;
93
+ layerViolations: number;
94
+ missingExpectedEdges: number;
95
+ errors: number;
96
+ warnings: number;
97
+ rules: number;
98
+ }
99
+ export interface StructureReport {
100
+ /** The tool ran and produced this report; read `verdict` for the outcome. */
101
+ ok: boolean;
102
+ ran: boolean;
103
+ verdict: Verdict;
104
+ verdictReason: string;
105
+ /** The versioned report contract this result follows. */
106
+ schema: ReportSchema;
107
+ notation: StructureNotation;
108
+ graph: StructureGraph;
109
+ /** Per-edge matrix verdict, present only when a matrix was supplied. */
110
+ edgeVerdicts?: StructureEdgeVerdict[];
111
+ findings: Finding[];
112
+ summary: StructureSummary;
113
+ /** Hashes of the inputs this report is about: the diagram text, and the matrix when one was supplied. */
114
+ hashes: {
115
+ diagram: string;
116
+ matrix?: string;
117
+ };
118
+ warnings: string[];
119
+ nextSteps: string[];
120
+ }
121
+ /** A finding code in the structure family; the numbers continue the UML0xx series after UML019. */
122
+ export type StructureFindingCode = 'UML020_ISOLATED_NODE' | 'UML021_DANGLING_REFERENCE' | 'UML022_CYCLE' | 'UML023_DISALLOWED_EDGE' | 'UML024_LAYER_VIOLATION' | 'UML025_MISSING_EXPECTED_EDGE' | 'UML026_NO_NODES';
123
+ /**
124
+ * Parse a structure diagram (PlantUML component/package/class/deployment, Mermaid
125
+ * class diagram) into nodes and dependency edges.
126
+ *
127
+ * A structure diagram is *not* refused here: refusing is the state-machine front
128
+ * end's job (`parseUml`). This parser is the honest reading of the same text.
129
+ */
130
+ export declare function parseStructure(text: string, notation?: 'auto' | StructureNotation): StructureGraph;
131
+ /** `*` matches any run of characters, `?` exactly one. Ids keep their own punctuation. */
132
+ export declare function globMatches(pattern: string, value: string): boolean;
133
+ export declare function validateMatrix(input: unknown): {
134
+ ok: true;
135
+ matrix: DependencyMatrix;
136
+ } | {
137
+ ok: false;
138
+ errors: string[];
139
+ };
140
+ /** Shortest directed cycle through the graph, as a node path with the start repeated. */
141
+ export declare function shortestCycle(nodes: string[], edges: StructureEdge[]): string[] | undefined;
142
+ export interface StructureReviewOptions {
143
+ /** Diagram text to review. */
144
+ diagram: string;
145
+ notation?: 'auto' | StructureNotation;
146
+ /** Dependency matrix: allowed/denied edges, layers, required edges. */
147
+ matrix?: unknown;
148
+ }
149
+ /**
150
+ * Audit a structure diagram. Every check is structural: it reads the drawn graph and
151
+ * the declared matrix, and never claims anything about the code behind them.
152
+ */
153
+ export declare function reviewStructure(options: StructureReviewOptions): StructureReport;
154
+ /** The finding codes this module can emit, for documentation and tests. */
155
+ export declare const STRUCTURE_FINDING_CODES: readonly StructureFindingCode[];
156
+ export type { VerdictSummary };
@@ -5,7 +5,7 @@ export declare const LOGICPROBE_VERIFY_TOOL_NAME = "logicprobe_verify";
5
5
  * the 22-check report (S1-S8 structural, A1-A14), or a 26-check
6
6
  * report when beforeModel is supplied (D1 behavioral preservation, D2
7
7
  * invariant continuity, D3 regression delta, D4 deadlock/liveness regression).
8
- * This is the dsh-native replacement for hand-filling the Python template
9
- * shipped in the skill references.
8
+ * This is the dsh-native path: the model is built and run here, with no
9
+ * template to fill in by hand.
10
10
  */
11
11
  export declare const logicProbeVerifyTool: import("@deepseek-ai/dsh-tools").ToolDefinition;