Skip to content

Patch authoring

A Patch provider is an ordinary DSH plugin that declares one or more CommonJS modules. Harmony discovers those modules from the selected Loader profile before target plugins execute.

Provider declaration

Add Harmony metadata to the provider's package.json:

json
{
  "name": "my-dsh-plugin",
  "dsh": {
    "harmony": {
      "patches": ["./patches/answer.patch.cjs"],
      "after": ["base-patches"],
      "before": ["ui-patches"],
      "conflicts": ["legacy-patches"]
    }
  }
}

Patch files must be CommonJS modules. Harmony collects them synchronously during live Loader updates.

If the provider itself cannot run without Harmony, use the existing DSH dependency mechanism:

ts
export const inject = ['harmony']

Or add the service to its Loader row:

yaml
- id: my-plugin
  inject: [harmony]

Source Patch

Source Patches select TypeScript AST nodes and edit the current source through MagicString:

js
/** @type {import('dsh-harmony').HarmonyPatch} */
module.exports = {
  id: 'answer-value',
  target: {
    package: 'some-dsh-plugin',
    version: '^1.2.0',
    files: ['lib/index.js'],
  },
  select: 'FunctionDeclaration[name.name="answer"] NumericLiteral',
  expect: 1,
  apply({ node, sourceFile, edit }) {
    edit.overwrite(node.getStart(sourceFile), node.getEnd(), '42')
  },
}

select uses TSQuery. The callback receives:

FieldValue
patchStable key and provider owner
sourceSource produced by all earlier Patches
sourceFileParsed TypeScript AST
nodeCurrent selector match
editMagicString editor for the current source
tsTypeScript namespace

Positions passed to edit refer to the source received by this Patch. files contains alternative package-relative targets; Harmony selects the first existing file. version is a semver range, and expect requires an exact match count.

Semantic Patch

Named function declarations and class methods can be decorated without writing AST edits:

js
module.exports = {
  id: 'answer-after',
  target: {
    package: 'some-dsh-plugin',
    version: '^1.2.0',
    files: ['lib/index.js'],
    function: 'answer',
  },
  operation: 'after',
  handler({ result }) {
    return result + 1
  },
}
OperationBehavior
beforeRuns before the target and may return a replacement argument array
afterRuns after the target and may replace a synchronous or asynchronous result
aroundReceives invoke(args?) and controls whether and how the next layer runs
replaceReplaces the target through invoke(args?); only one enabled replacement may own a function

All before handlers run in Patch order. around and replace form an outer-to-inner chain in Patch order. All after handlers then run in Patch order. Source and semantic Patches share the same global provider order.

Semantic targets currently require named parameters and do not support generators. Handlers run in Node.js, so browser targets such as lib/client.js must use source Patches.

Ordering constraints

before and after refer to provider package names. They are sorting constraints, not npm or Cordis dependencies. The manual list remains authoritative; automatic sorting finds a minimum-violation order while preserving existing order when solutions tie.

Patches within one provider execute in declaration order. Providers execute in profile order. Every later source Patch receives the output of the earlier one.

Provider conflicts

conflicts declares provider incompatibilities. A one-sided declaration is enough. Harmony shows the warning only when both packages are enabled Patch providers in the current Loader Tree.

The warning does not block installation, startup, application, ordering, or reload. Disabling either provider with <provider>/* removes it.

Minimal WebUI example

The following Patch replaces the new-session headline in the compiled conversation client:

js
const headline = 'Harmony is All You Need'

module.exports = {
  id: 'home-banner',
  target: {
    package: '@deepseek-ai/dsh-client-ui-conversation',
    version: '0.1.0-rc.6',
    files: ['lib/client.js'],
  },
  select: 'StringLiteral[text="探索未至之境"]',
  expect: 1,
  apply({ node, sourceFile, edit }) {
    edit.overwrite(node.getStart(sourceFile), node.getEnd(), JSON.stringify(headline))
  },
}

WebUI new-session hero changed by a Harmony Patch

Service and tooling APIs

Plugins can inject the harmony service:

ts
export const inject = ['harmony']

export function apply(ctx) {
  const snapshot = ctx.harmony.inspect({ package: 'some-dsh-plugin' })
}

The service exposes:

  • binEntry and profileDir for the active runtime;
  • inspect(input?) for Patch status and transformed target snapshots;
  • inspectDependencies(owner) for relationships inferred between Patches;
  • prepareDraft(input) for an isolated Draft runtime.

The package also exports HarmonyDraftRuntime, extension discovery helpers, and their TypeScript types for downstream tooling.

Released under the MIT License.