Variable/Section, Variable/Default, Suppress, Policy, Refusal, Function/Update and Activate - consumed directly by the factory-era governance hooks (governor-package, pinner-package, governor-cargo).
hook-dsh-core
Hook @ DSH @ Core • _The DeepSeek Harness Plugin Family for PlayForm._ The pure machinery layer of the DSH plugin family: the dependency-free, harness-free commonalities that both halves of the family are built from - the governance hooks' helpers (Section/Default/Suppress/Policy/Refusal, the update-stage envelope and the activation-line composer) and the stream-normalization family's tables and dispatch (six Normalize tables + Replace/ReplaceMap + Stream/Chunk/Block).
$ pnpm add @playform/hook-dsh-core The profile wiring for this plugin - the bundles list, the patch entry and the restart - is on the setup page.
Where It Fits
Family position (the @-sentence Hook @ DSH @ Core ): the base library of the whole family - parent of none, child of none: zero runtime dependencies, consumed by all eleven other packages.
Six tables (Dashes, Quotes, Ellipsis, Spaces, Invisible, Fullwidth), the Replace/ReplaceMap replacers and the generic Stream/Chunk + Stream/Block dispatch - consumed by the six stream normalizers.
The core is not a plugin bundle: no cordis.patch.yml, no loader contract, publishable on its own - consumers link it as a plain library dependency, not as a bundle row. The DSH plugin family is the DeepSeek Harness plugin layer of the PlayForm ecosystem: TypeScript-first Source/ → Target/, the deterministic @playform build, prepublishOnly-only - the same conventions as every other @playform package.
In the DeepSeek Harness
The core is not a plugin and touches no seam itself - it is the shared machinery the family's plugins are made of, running wherever they run:
| Seam | What the plugin does there | What you can observe |
|---|---|---|
| llm/stream (the model stream) | Supplies the stream-normalization half the six flavors dispatch through: the six character tables, the two generic replacers and the per-chunk/per-block dispatch running inside each flavor's llm/stream listener. | Normalized text reaching the live UI and the durable transcript, byte-predictable per chunk. |
| fs/observed + fs/write-intent (the file events) | Supplies the governance half the three governance hooks compose their passes from: the section list, the exclusion fence, the policy loader, the refusal guard and the update-stage envelope. | Governed manifests with the write's tool result unchanged (the author never learns). |
| The ledger / session | Owns no ledger strings and writes nothing - every consumer logs through its own Append; the core only supplies the byte-exact mechanics (the suppression composer, the diagnostics, the refusal line, the activated(...) template). | Identical line shapes across all twelve plugins, in each plugin's own ledger. |
The Problem
The family's hooks kept re-implementing the same pure functions: the NPM section list, the exclusion segments, the suppression-line composer, the policy loader, the refusal guard - and, for the normalize flavors, the same per-chunk dispatch, block-end normalizer and character tables, copy-pasted per flavor. Every fix had to be applied N times. The core makes each of them exist once, importable by any consumer - inside the DeepSeek Harness or entirely outside it.
How It Works
The dispatch owns the structure (which fields of which chunk types); the flavor owns the substitution - a new normalization flavor is a table plus a closure, not a fork of the dispatch. An entire flavor's transform leaf:
The helpers, exactly. Section - the canonical NPM dependency-section list (dependencies, devDependencies, peerDependencies, optionalDependencies). Default - the built-in EXCLUSION segments (node_modules, .git, .dsh, .pnpm, .store, DeepSeek Harness.app). Suppress(module, kind, cause) - the suppression-line composer ( module: kind error (suppressed): cause, kind is "listener" or "continuation") - the byte-identical line every contained throw produces (logger-only, never the ledger). Policy(append, file, defaults, quiet?) - the update-policy loader: plain-fs read when given and readable; any failure falls back to the module's built-in defaults with the family's byte-identical diagnostic lines. Refusal(...) - the shared REFUSAL GUARD: only the declared dependency sections may differ; a difference anywhere else composes the byte-identical refusal line and the rewrite is never applied. Update(dependencies) - the UPDATE-STAGE ENVELOPE (the shared Dispatch/Settle pair: gates, P2 registration, jobs envelope, P5 records, U2 refresh). Activate(fields) - the activation-line composer: the activated (...) proof line's shared string mechanics; the field list stays module-side. Replace /ReplaceMap - the two generic replacers: a fresh per-call regex (no shared lastIndex state), a function replacer ($ patterns are never interpreted), and the { text, count } contract where count === 0 means "unchanged" and the caller keeps the original by identity. Chunk / Block - the generic dispatchers, structural in their chunk/block shapes, with the transform injected: pure, total on any string, order-preserving, no buffering.
In Action
Before - a governance hook hits a contained throw and a guarded write
After - the byte-identical lines every consumer produces
And the normalize half, the two generic replacers at work (the inputs carry the real typographic characters the flavor tables match):
Before and after - a stream flavor's transform leaf, called directly
count === 0 means "unchanged": the caller keeps the original string by identity instead of allocating a copy. The policy loader, in its Quiet form, reads the same update-policy.json the update engine will honor and returns the built-in defaults - with the diagnostics, or without them.
The Config
The core registers no Config and needs none - it is not a plugin: no loader contract, no context, no Schemastery schema, nothing to validate at load. Every knob it exposes is a function parameter (Policy's defaults, Replace's replacement, Chunk's injected transform), decided by the consuming module's own config.
The Ledger
The core owns no ledger strings and writes nothing - no plugin, no logger, no ledger file. The module ledger strings stay MODULE-side: the core supplies the mechanics (the Suppress composer, the Policy diagnostics, the Refusal line, the Activate template), and every consumer logs them through its own Append (the factory's ledger service). Nothing in the core ever composes a <Module>: prefix or an activation proof's field list - the activated (...) template is the mechanics only; the fields are the module's.
Related plugins
The family's parent service - deliberately re-exports NOTHING from the core (its service surface stays stable); the hooks import the core's helpers directly.
The first of the six stream normalizers - a table plus a closure over Replace, dispatched through Chunk/Block.
Consumes the governance half: Suppress in the listener's catch, Policy in the update engine, Refusal in the transform.
License: MIT. The unit smoke (core-smoke.mjs in the family's smokes/ directory) exercises every helper with no fake context at all.