/** * The agent-facing content harnery ships into a consumer: one orientation block * for `AGENTS.md` and the generic skills. Everything here is engine mechanics * only — no triage rubric, no escalation targets, no host doc-layout policy * (those stay host-authored, per ADR 0007's portability split). Every command * string renders through `binName`; a template that only reads for `harn` is a * bug the portability guard exists to catch. * * Content, not paths: templates are TS string builders (not shipped `.md` * files), so they compile into `dist/` and resolve identically under Bun and * Node — no `files`-field copy or package-path guesswork. */ import { JOURNAL_CATEGORIES } from "../../core/journal/index.ts"; import { buildOwnedSkill } from "./splice.ts"; /** Managed-region name for the AGENTS.md orientation block. */ export const INSTRUCTIONS_REGION = "instructions"; /** Managed-region name for the CLAUDE.md `@AGENTS.md` import shim. */ export const IMPORT_REGION = "import"; /** * Managed-region name for the consumer's own coordination policy, spliced from * the file named by `instructions.hostAddendumFile`. Harnery places and * versions the region; the content is the host's and is never rendered here. * See `host-addendum.ts`. */ export const HOST_ADDENDUM_REGION = "host-addendum"; /** Which shipped skills exist in the project the block is rendered for. */ export interface BlockSkills { /** the `harn-decide` skill file is present */ decide: boolean; /** the `harn-council` skill file is present */ council: boolean; /** the `harn-end` skill file is present */ end: boolean; /** the `harn-team` skill file is present */ team: boolean; } /** * The always-on orientation spliced into `AGENTS.md`. Target ≤ 80 rendered * lines: it costs every agent context on every turn, so it states that each * surface *exists* and gives one line of *when* — the *how* lives in the skills * and each command's `--help`. Skill names are fixed (`harn-decide`, * `harn-council`, `harn-end`) even for a renamed bin; only command strings * track `binName`. * * The block only points at a skill that actually exists here: a host that * excludes one via `skills.exclude` gets a CLI pointer instead of a dangling * reference to a skill it doesn't have. */ export function renderInstructionsBlock( binName: string, skills: BlockSkills = { decide: true, council: true, end: true, team: true }, ): string { const b = binName; const named = [ skills.decide && "`harn-decide`", skills.council && "`harn-council`", skills.end && "`harn-end`", skills.team && "`harn-team`", ].filter(Boolean) as string[]; const deeper = named.length > 0 ? `Procedures for the deeper flows live in the ${named.join(" and ")} skill${named.length > 1 ? "s" : ""}.` : `See \`${b} decision --help\` and \`${b} agents council --help\` for the deeper procedures.`; const decidePointer = skills.decide ? "The `harn-decide` skill has the file / claim / resolve-with-evidence procedure." : `See \`${b} decision --help\` for the file / claim / resolve-with-evidence procedure.`; const councilPointer = skills.council ? "The `harn-council` skill has the steward and member flow." : `See \`${b} agents council --help\` for the steward and member flow.`; const endPointer = skills.end ? "When the whole session is genuinely finished, use the `harn-end` skill as the final workflow." : `When the whole session is genuinely finished, run \`${b} agents status --end-turn --end-session\` as the final tool action.`; const teamPointer = skills.team ? "The `harn-team` skill owns the tier choice, the governor artifacts, and the drive loop." : `See \`${b} governor --help\` for the tier choice and the drive loop.`; // Render the journal categories from the canonical enum so this prose can // never drift from what `journal add` actually accepts (the "note, plan…" list // silently lagged the tool by two categories before this). const journalCats = JOURNAL_CATEGORIES.length > 1 ? `${JOURNAL_CATEGORIES.slice(0, -1).join(", ")}, or ${JOURNAL_CATEGORIES.at(-1)}` : JOURNAL_CATEGORIES[0]; return `## harnery coordination This project runs [harnery](https://harnery.com) for multi-agent coordination. You share this checkout with other agents; the surfaces below keep you oriented and out of each other's way, and let you dispatch a team of your own when a job is bigger than one session. Run \`${b} --help\` for any command's full surface. ${deeper} **Identity + peers.** You are one of several agents in this repo. \`${b} agents whoami\` is you; \`${b} agents status\` shows your session plus the active peers and the files they've claimed; \`${b} agents set-task ""\` declares your current focus so peers can see it. Check for peers before editing widely-shared files. **Task lifecycle.** Beside the activity peers already see, declare whether your objective is still open: \`${b} agents lifecycle blocked --reason ""\` when it cannot proceed, \`${b} agents lifecycle done\` when it is complete, and \`${b} agents lifecycle active\` to reopen. \`done\` requires a current task and a passing Git finalization check (dirty or unpushed work refuses, and nothing is written). Ordinary \`set-task\` calls never change lifecycle, and a transition that re-mints the session title tells you the new name to copy. ${endPointer} **Dispatching a team.** Everything else here coordinates the agents already present. These three start new ones, and they differ by how long the objective outlives a single execution. \`${b} run