PLUGIN DETAIL ROLE: CORE NOT A PLUGIN - THE MUSCLE UNDER THE PLUGINS

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).

CLI INSTALL COPIED
$ pnpm add @playform/hook-dsh-core
Namespace: @playform/hook-dsh-core Release: v0.0.1
Archetype: Library Halves: Governance + Normalize Runtime deps: ZERO

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 BASE LIBRARY

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.

The governance half GOVERNANCE

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).

The normalize half NORMALIZE

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

WHERE THE MACHINERY RUNS

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:

SeamWhat the plugin does thereWhat 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 / sessionOwns 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

WHY THE CORE EXISTS

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

TWO HALVES, ONE ENTRY
@playform/hook-dsh-core (named exports - no plugin, no loader contract) │ ├─ GOVERNANCE HALF ──────────────────────────────────► consumed by │ Variable/Section the four NPM dependency sections the factory-era hooks │ Variable/Default the built-in exclusion segments (governor-package, │ Suppress(module, kind, cause) suppression composer pinner-package, │ Policy(append, file, defaults, quiet?) policy loader governor-cargo) │ Refusal(append, path, sections, current, next) refusal guard │ Function/Update(deps) → { Dispatch, Settle } the update-stage │ envelope: the shared Dispatch/Settle pair - a new governance │ module's update stage reduces to a child runner plus strings │ Activate(fields) the activation-line composer: the `activated (...)` │ proof line's string mechanics (prefix, `k=v` join with `, `, │ suffix, skip-absent); the field list stays module-side │ └─ NORMALIZE HALF ──────────────────────────────────► consumed by Normalize/Dashes the hermes dash class (verbatim) the six stream Normalize/Quotes curly → straight MAP (8 entries) normalizers Normalize/Ellipsis U+2026 class (normalize-dash, quotes, Normalize/Spaces Zs-minus-ASCII class ellipsis, spaces, Normalize/Invisible zero-width/format class invisible, Normalize/Fullwidth FF01-FF5E → 21-7E MAP (94 entries) fullwidth) │ ▼ Normalize/Replace class → string, function replacer, per-call regex ("gu"), { text, count } Normalize/ReplaceMap char → char MAP, keys as code-point escapes, function replacer, { text, count } │ ▼ Stream/Chunk the generic per-chunk dispatch (text-delta, reasoning-delta?, tool-call-delta?, block-end; everything else passthrough by identity - count 0 keeps the original chunk BY IDENTITY; the tool-args gate is three-way with the flag on: `edit`, `raw-write` and `normalize-file` calls pass through BY IDENTITY and a raw-marked call (`{"__normalize":false` first-key marker, tracked per call id and passed as `Raw`) passes through UNNORMALIZED with the marker stripped - see Stream/Strip) Stream/Block the generic block-end normalizer (TextBlock text; ReasoningBlock text + a runtime thinking string field; ToolCallBlock arguments? with the same three-way gate); everything else passthrough by identity

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:

Replace(Text, Dashes, Replacement); // class flavor ReplaceMap(Text, Quotes); // map flavor CoreChunk(Input, (Text) => Replace(Text, Dashes, "-"), Reasoning); // + a 4th ToolArgs flag (default off) and a 5th Raw flag (the per-call // `{"__normalize":false` marker state) for the tool-call cases

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

THE HELPERS IN A CONSUMER'S CODE

Before - a governance hook hits a contained throw and a guarded write

Context.logger.warn(Suppress("hook-dsh-governor-package", "listener", String(Error))); // -> "hook-dsh-governor-package: listener error (suppressed): cannot get property ..." if (Refusal(Append, Path, Section, Document, Next)) { return null; // the byte-identical line went to the ledger, the write did not happen // -> `REFUSED rewrite of <path>: non-dependency section "scripts" would change` }

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

Replace("first — then 10–15 total", Dashes, "-"); // -> { text: "first - then 10-15 total", count: 2 } ReplaceMap("he said ‘hello’ — loudly", Quotes); // -> { text: "he said 'hello' - loudly", count: 2 } // (the em dash is the Dashes class's job, not the Quotes map's)

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

NONE - NOT A PLUGIN

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.

import { Suppress, Refusal, Section, Default, Policy } from "@playform/hook-dsh-core"; import { Replace, ReplaceMap, Dashes, Chunk } from "@playform/hook-dsh-core";

The Ledger

THE CORE OWNS NO LEDGER STRINGS

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

12 TOTAL

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.