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

Use ProjectContext.files to read staged files or propose writes, copies, templates, and deletion. The file operation reference explains the result of each operation. 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 ProjectContext.yaml to read configuration and propose field edits while retaining unrelated values. 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.
Local edits block managed replacement. YAML renames instead carry the user’s current value forward.

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; path is relative to the generated agent. Sources must be regular files inside the template directory and included in the distributed package. Each operation copies one file for teammates; these documents are excluded from compilation. Use agent templates for model-facing instructions and skills. Use policy=WritePolicy.MANAGED in a migration to update an unchanged pack-owned document. Local edits block replacement.

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.