Skip to main content
Return a ChangePlan from your initializer or migration. It holds ordered operations; constructing it does not write files.
Register a function like this with the appropriate pack decorator. Return ChangePlan() when a hook has no work to propose.

File operations

Paths must be canonical project-relative paths. Absolute paths, traversal, source symlinks, and reserved state paths such as .git/, .harnest/, and harnest-packs.lock are rejected. File/directory conversions require manual migration.

YAML operations

Use tuples for key paths. ("deployment", "region") identifies a nested field; ("deployment.region",) identifies a literal key containing a dot. A rename cannot move a field into itself or its descendants. A scalar parent is a conflict, not an object to replace implicitly.
YAML documents must be mappings with unique keys and a single document. Edits preserve unrelated values but normalise formatting and do not preserve comments.

Write policies

Use the enum values from harnest.authoring, rather than strings: IF_MISSING is the default for file writes, verbatim copies, templates, and YAML sets. MANAGED is appropriate when a later pack release updates a generated workflow or an unchanged default.
A local modification blocks replacement even if the pack intends to write the same value. YAML rename operations are different: they carry the user’s current value forward instead of resetting it to a generated default.

Add team documentation to the project

Teams can copy Markdown guides, API references, runbooks, PDFs, images, and other files into generated projects. Use from_file to preserve the original bytes without decoding text or substituting dollar expressions. Set templates on your ProjectPack to the directory containing the packaged assets:
source is relative to the pack’s templates directory. The destination path is relative to the generated agent. The source must resolve to a regular file inside the pack’s template directory; missing files and escaping paths block the operation. Include these asset files when distributing the team’s Python package. Each operation copies one file. These documents remain in the generated project for teammates and are excluded from compilation. The compile manifest has no resources field. Use an agent template to create standard instructions.md or skill content when the agent itself needs guidance. Copied files use the same ownership records, conflict checks, and migration policies as text files. In a versioned migration, use policy=WritePolicy.MANAGED to update an unchanged pack-owned document. Local edits block replacement instead of being overwritten.

Templates

Set templates=Path(__file__).parent / "templates" when defining your pack. Without values, template content is copied literally, including dollar signs. Supplying values enables Python string.Template substitutions:
team-notes.txt
Provide every referenced variable and use $$ for a literal dollar sign when substitution is enabled. Missing templates or substitutions fail the hook and block application.

Combine packs

Different packs can own separate fields in the same YAML file. Ownership of a whole file overlaps all fields inside it; ownership of a parent key overlaps its descendants. Conflicting proposals block the combined plan even if their values are identical. Core Harnest migrations run first. A core rewrite of a pack-owned file also requires review because the planner does not assume that the two migrations are compatible. See resolve blockers.