.agents/skills/migrate-internal-package/SKILL.md
Move a package from another TryGhost repository into Ghost without losing its history or creating a period where neither repository owns it. Keep migration mechanics separate from Ghost's lifetime package standards.
Explain every cross-repository or administrative action before it happens.
Never manipulate history in a checkout containing unrelated work. Fetch both
repositories, confirm the source checkout is clean, and create a dedicated
Ghost worktree from the freshly fetched origin/main.
Run history-changing commands individually or in a fail-fast shell. A failed
git worktree add must not be followed by git subtree add in whichever
checkout happens to be current. Before choosing a branch or path, list existing
worktrees and matching local and remote branches. Use a migration-specific slug,
for example codex/import-<package>-from-<source>, rather than a generic name.
If the destination branch or path already exists, stop and inspect its cleanliness, base, divergence, source split and attached worktree. Do not mutate, delete or silently reuse it. Present the evidence and ask the user whether to resume, preserve and supersede, or remove it when more than one choice is reasonable. A previous attempt can contain valid unmerged history even when its remote branch is gone.
Before importing, record and compare the destination HEAD and origin/main;
they must match. Recheck the first parent immediately after the subtree commit.
Before changing either repository, record the source repository, its default branch, the package path within it, the destination path in Ghost, and whether the package has been published to npm. Then:
workspace:*.Publication or download counts alone do not prove that a package must remain a supported public API. If supported external consumers still need new releases, stop: this internal-only workflow is the wrong publishing model.
For the public-consumer audit, check npm metadata, first-party repositories and documentation, plus GitHub code search for package dependencies and runtime imports. Classify results as first-party consumers, independent integrations, Ghost forks, or deployed Ghost installation snapshots. Forks and installation snapshots show historical presence only. Require a recent update, current deployment, active dependency or another freshness signal before treating them as evidence of continued installation needs; even then, they do not establish an independently supported API. Record the evidence and confidence behind the ownership decision; stop and ask if current support expectations remain unclear.
Run registry-only npm view commands from a neutral temporary directory. A
repository's devEngines policy can reject the host Node version before npm
contacts the registry, which is unrelated to the package metadata audit. Record
that failure separately if using a neutral directory does not resolve it.
Keep the history import and modernization separate. The import establishes ownership and provenance; later commits can modernize code without obscuring the move.
Read
references/history-and-merge.md completely
before manipulating history.
Use an unsquashed git subtree import from the recorded source repository and
package path. Do not copy the current files, pass --squash, or recreate old
commits manually.
After the subtree commit, add focused integration commits that:
ghostPackage.goldenPath to migration and ghostPackage.reason to a
concise explanation of the remaining modernization work;workspace:*;pnpm;Before editing package metadata, map each source workspace:* dependency to
its destination state:
workspace:* when the dependency already exists in Ghost;catalog: when Ghost's catalog version satisfies the imported package;Document temporary named-catalog entries for the modernization follow-up. Never inline dependency versions; Ghost's strict catalog policy still applies. Do not import additional packages implicitly.
If the package is legacy JavaScript or CommonJS, read
references/legacy-integration.md
completely before creating integration commits.
Verify that the subtree commit has two parents and that representative file history crosses into the source repository before opening the PR.
Before opening the PR, also verify the source split is reachable from the branch tip, the consumer resolves the workspace package through its production import path, package lint and tests pass through Nx, relevant consumer tests pass, the full build passes, and the Ghost archive contains the internal package. Record the exact commit IDs and commands in the handoff.
For a pilot or first use, include a structured gap report in the handoff:
Observed: the exact failure or ambiguity and the command/state that exposed it;Worked around: the safe action taken, without hiding the original gap;Skill change: the concrete instruction, preflight or script improvement;Tooling change: anything that cannot be solved within this repository;Confidence: high, medium or low, with unresolved evidence called out.This is the exceptional PR. It must use GitHub's Create a merge commit method:
Make CI green, then stop at the merge checkpoint. If the merge-commit option is disabled, an authorized org admin should run:
.agents/skills/migrate-internal-package/scripts/merge-history-pr \
TryGhost/Ghost \
<pr-number> \
<source-split-tip> \
--confirm
An agent without admin authority should provide that exact handoff rather than attempting a workaround. The script performs preflight checks, records and temporarily changes the setting, uses the correct merge form, restores the setting, and verifies the resulting history.
Afterward, independently fetch main and confirm the source split tip is an
ancestor before starting source-repository cleanup.
Branch from the latest source default branch only after the Ghost import is
verified on main. Remove:
Search with both the npm name and directory name. The source-repository PR should link to the merged Ghost PR and state that Ghost now owns the implementation. Use that repository's normal merge policy; this PR does not contain imported ancestry.
If the package was published and new direct use is now unsupported, deprecate all historical versions rather than unpublishing them:
npm deprecate '@tryghost/<package>@*' \
'This package is now maintained as an internal Ghost workspace package. Existing versions remain available for older Ghost releases; direct use is unsupported.'
npm view @tryghost/<package> deprecated
This preserves installation for old Ghost releases. Authentication and OTP are human checkpoints; verify the public metadata after the authorized user runs the mutation.
Search .github/renovate.json5 and related release automation for rules that
still treat the package as sourced or released from the former repository or
npm. Prefer a small,
standalone cleanup PR so the later conversion remains focused.
Read packages/README.md completely and compare the package against the
template and current comparable internal packages. Do not turn this one-time
migration skill into the source of lifetime package standards.
If the package needs conversion from legacy JavaScript or CommonJS, use the
convert-internal-package-to-typescript skill in a separate modernization PR.
That skill owns commit staging, file-lineage checks, TypeScript quality and
runtime verification. This does not alter the merge-commit requirement for the
earlier subtree history import.
Do not call the migration complete until:
main;At each boundary, distinguish what is merged from what is merely prepared and report the exact verification performed.
Do not use this workflow for packages that remain public, independently versioned, or supported for third-party use. This skill describes privileged operations but does not authorize them.