name: dops-convert description: Convert a DeltaPorts compat port (Makefile.DragonFly with conditionals/.for, non-variable Makefile.diff hunks, and other diffs/ artifacts the deterministic translator could not handle) into an engine-valid overlay.dops, offline. Use for the Step 48 mass-convert "blocked"/"deferred" tail. Produces a written overlay.dops validated against the dops engine; faithfulness is the steady-state build loop's job, not yours.
DeltaPorts compat → dops conversion (offline)
What this skill is for
The Step 48 mass-convert already handled the deterministic bulk. You get
the tail the deterministic translator escalated: ports whose
Makefile.DragonFly has .if/.elif/.else/.for, whose Makefile.diff
has recipe/conditional hunks, or that carry odd diffs/ artifacts. Your
job is to translate each port's compat artifacts into a single
overlay.dops that the dops engine accepts (parses + checks + plans).
Your success bar is ENGINE-VALID, not faithful. A converted port's output need not byte-match compat. The steady-state build loop verifies behavior and fixes residual divergence later. So: capture the intent as best you can with real ops, make it engine-valid, move on. Do not try to build anything — there is no build env here.
The hard rules (these bite silently — read first)
No partial absorption. Writing
overlay.dopsflips the port to dops-mode, which suppresses the entire compat path. So you MUST absorb every compat artifact of the port into the one overlay:Makefile.DragonFly, alldiffs/*, anddragonfly/. If you cannot handle even one artifact, escalate the whole port — do not write a partial overlay (it would silently drop the unhandled artifact's effect).dragonfly/STAYS on disk.dragonfly/*are upstream-source patches; the overlay references them viafile materializeand the engine reads them fromdragonfly/at compose time. Emitfile materialize dragonfly/X -> dragonfly/Xfor each file and do NOT deletedragonfly/— it is the materialize source. Neverpatch applyadragonfly/*file (no extracted source exists).Delete what you absorbed. After writing the overlay, delete
Makefile.DragonFly*and everydiffs/*file you translated (and the now-emptydiffs/dir). Keepdragonfly/. EXCEPTION —patch apply diffs/X: if your overlay references a diff viapatch apply diffs/X, that diff is NOT absorbed into ops — it is applied verbatim at compose time, so KEEP it. Deleting it makes apply fail withE_APPLY_MISSING_SUBJECT(patch file does not exist). Only deletediffs/*files whose effect you fully translated into mk/text ops.STATUS + type. Read
ports/<origin>/STATUSfirst line:PORTor absent → headertype port, and delete STATUS.MASK/DPORT/LOCK→ headertype mask|dport|lock(must match), and keep STATUS.
target @anyis the default and correct for unscoped artifacts — an unscoped fragment applies on every branch. Only use@main/@2026Qnif the source artifact is itself target-scoped (Makefile.DragonFly.@2026Q2,diffs/@2026Q2/...).
File shape
port <category>/<name>
type port
reason "<one-line why>"
target @any
# ops, each on its own logical line
Op grammar (authoritative: scripts/generator/dportsv3/agent/dops_quickref.md)
# variables (source operator -> op: = ->set, := ->eval, != ->shell, += ->add)
mk set VAR "value" # `=` — always quote the value
mk eval VAR "${VAR:mod}" # `:=` — immediate / self-referential (see below)
mk shell VAR "cmd ${X}" # `!=` — shell command substitution
mk add VAR token # `+=` — quote tokens with >,:,",spaces
mk remove VAR token
mk unset VAR
# `?=` has NO faithful op -> escalate (do not render as `mk set`)
# conditionals
mk disable-if condition "${OPSYS} == FreeBSD" # edits an EXISTING upstream .if
mk replace-if from "${OPSYS} == FreeBSD" to "${OPSYS} == DragonFly" # edits an EXISTING upstream .if
mk block set condition "${DFLYVERSION} >= 400706" <<'MK' # INSERTS a new .if (runtime guard)
PLIST_FILES+= include/foo/bar.h
MK
# framework-var (${DFLYVERSION}/${OPSYS}/${OSVERSION}) condition inserted into a
# terminal-only port? emit `mk ensure-include bsd.port.options.mk` FIRST (below)
mk ensure-include bsd.port.options.mk
# recipes
mk target set post-extract <<'MK'
@${REINPLACE_CMD} -e 's,a,b,' ${WRKSRC}/configure
MK
mk target append pre-configure <<'MK'
@${ECHO_CMD} hi
MK
mk target remove pre-install
mk target rename do-install -> do-install-dragonfly
# files / text
file materialize dragonfly/patch-X -> dragonfly/patch-X # stage upstream-source patch
file remove files/patch-stale on-missing noop
text line-remove file Makefile exact "BROKEN= x" on-missing noop
text line-insert-after file pkg-plist anchor "<prev line>" line "<new>" on-missing noop
text replace-once file Makefile from "<old single line>" to "<new single line>"
# last resort for a gnarly diffs/*.diff (framework files only, NOT dragonfly/)
patch apply diffs/X.diff
Most ops accept on-missing error|warn|noop (default error). Use
noop when the change may already be absent on some branches.
Per-artifact conversion recipes
Makefile.DragonFly (the fragment — its lines are appended to the port Makefile):
VAR= x→mk set VAR "x"VAR:= x→mk eval VAR "x"(immediate — see self-referential note below)VAR!= cmd→mk shell VAR "cmd"(shell command substitution)VAR?= x→ escalate — no faithful op.mk setwould override an upstream value the?=default was meant to defer to.VAR+= a b→mk add VAR a+mk add VAR b(ormk add VAR "a b").undef VAR→mk unset VAR.if COND ... .endifblock →mk block set condition "COND" <<'MK' ... MK, ormk replace-if/mk disable-ifif it edits an existing upstream.if. If COND references a framework var (${DFLYVERSION}/${OPSYS}/${OSVERSION}) and the port is terminal-only (only.include <bsd.port.mk>), emitmk ensure-include bsd.port.options.mkbefore the block, else it fails withVariable "DFLYVERSION" is undefined. And never invent an${OPSYS} == DragonFlyguard — the composed tree is DragonFly-only, so emit DragonFly content unconditionally.- Named pattern —
.if !defined(DPORTS_BUILDER)guard (very common):.if !defined(DPORTS_BUILDER)/MANUAL_PACKAGE_BUILD= .../.endif→mk block set condition "!defined(DPORTS_BUILDER)" <<'MK'…body…MK - Self-referential assignment
VAR:= ${VAR:mod}(filter:N/:M, substitute:S/:C, prepend, append — the value expands the same var) →mk eval VAR "${VAR:mod}". This appends a verbatim immediate:=line and is faithful for EVERY modifier. Do NOT usemk setfor a self-referential value — it renders a fatal recursive=("Variable X is recursive"). target:recipe →mk target set target <<'MK' ...recipe... MK.for ... .endfor→ usually genuinely hard; if you cannot express it cleanly, escalate the port.
diffs/Makefile.diff (unified diff vs the port Makefile):
- in-place variable change →
mk set/add/remove .ifcondition change →mk replace-if from "..." to "..."- single-line change →
text replace-once file Makefile from "..." to "..." - recipe block change →
mk target set - if too gnarly →
patch apply diffs/Makefile.diff(engine applies it against the materialized Makefile — valid fallback fordiffs/only)
diffs/REMOVE → one file remove <path> on-missing noop per line.
diffs/pkg-message.diff / diffs/pkg-descr.diff → text replace-once
per changed line, or patch apply diffs/X.diff.
diffs/*.in.diff / diffs/files_*.diff (patch a port template) →
patch apply diffs/X.diff (framework-file domain).
dragonfly/* → file materialize dragonfly/X -> dragonfly/X for each
file. KEEP dragonfly/.
diffs/pkg-plist.diff → line ops on pkg-plist: removals →
text line-remove file pkg-plist exact "<line>" on-missing noop;
single-line changes → text replace-once file pkg-plist from "<old>" to "<new>";
additions → text line-insert-after file pkg-plist anchor "<preceding line>" line "<new>" on-missing noop
(anchor must be a line unique in the upstream pkg-plist — if not, escalate).
NOTE: pkg-plist order is load-bearing (@mode/@owner/@group apply to
following lines; @exec/@unexec run in order) — keep edits in place;
never reorder.
diffs/distinfo.diff → distinfo is generated checksum data; if you
can't reproduce it as ops, escalate (don't guess hashes).
Workflow per port
ls ports/<origin>/— enumerate artifacts (Makefile.DragonFly*,diffs/*,dragonfly/*,STATUS).- Read each artifact. Translate per the recipes above into one op list. If ANY artifact is beyond you → escalate (skip; report it), do not write a partial overlay.
- Write
ports/<origin>/overlay.dops(header + ops). - Validate against the engine (this is your only correctness gate):
If notscripts/generator/.venv/bin/python -c "from dportsv3.engine.api import build_plan; from pathlib import Path; p=Path('ports/<origin>/overlay.dops'); r=build_plan(p.read_text(), p); print('OK' if r.ok else [(d.code, d.message) for d in r.diagnostics])"OK, fix the ops from the diagnostics and re-run untilOK. (Common: unquoted value with>/:/"; heredoc tag mismatch; invalid op syntax — consult dops_quickref.md.) - Delete absorbed artifacts:
Makefile.DragonFly*, the translateddiffs/*files, emptydiffs/, andSTATUS(only if type=port). Keepdragonfly/. - Record the result (origin, converted/escalated, one-line note).
Ambiguous variables (multiply-defined upstream)
mk set / mk remove edit an existing assignment in the upstream
Makefile. If the target variable is assigned more than once upstream
(e.g. CMAKE_ARGS defined = then +=, or OPTIONS_DEFAULT defined
twice), the op is ambiguous and fails at compose-apply time with
E_APPLY_AMBIGUOUS_MATCH — even though build_plan (engine-valid) passes.
contains is NOT valid on mk set/mk add/mk remove (only on
mk disable-if/mk replace-if/mk block set). To handle a multiply-defined
var, pick by INTENT:
- append a token (the DragonFly fragment used
VAR+= tok) →mk add VAR tok.mk addis never ambiguous — on a multiply-defined var it appends a freshVAR+= tokline, which accumulates correctly. - replace the whole value (the fragment used
VAR= ...orVAR:= ..., i.e. a trailing override) →mk eval VAR "...". This appends an immediateVAR:= ...line that overrides all prior definitions — faithful to the fragment's replace intent. (Check the original operator:+=⇒mk add,=/:=⇒mk eval.) - multiply-matched
.ifblock (amk block/disable-if/replace-ifcondition matches more than one upstream.if) → addcontains "<substring unique to the intended block>". - if you can't pin it, escalate the port.
A text replace-once whose from string occurs more than once also fails
ambiguous — use a longer from that is unique in the file, or escalate.
Note: build_plan is necessary but NOT sufficient — it plans the overlay
but never applies it against the real upstream Makefile. Ambiguous-match
and missing-subject errors only surface at compose-apply, which is the
authoritative gate the main agent runs per batch.
Escalation
Escalate (leave the port untouched, report it) when:
- a
.forloop or deeply nested/computed conditional has no clean dops form, - a
distinfo.diffneeds hashes you can't derive, - a
pkg-plistaddition has no unique anchor, - the overlay won't go engine-valid after a couple of honest attempts.
Escalation is a fine outcome — a port we leave compat is safe; a port we half-convert is not.
Reporting
Per batch, return: count converted vs escalated, the escalated origins with one-line reasons, and any recurring pattern worth folding back into this skill (note under "Skill update suggestions").