Project crest seal pressed into glowing amber wax, customization as branding the forge with your project's emblem
Chapter 9

Customization

Make it yours: principles, profiles, custom instructions, configuration hierarchy.

The Two-Layer Model

Every project gets two layers of guardrails. Layer 1 is your non-negotiable standards, the rules every project gets whether they ask or not. Layer 2 is your project's specific ambitions, the coverage targets, latency SLAs, and domain rules that make this project different from the last one.

Layer 1, Universal Baseline

Ships with every preset. Architecture, security, testing, error handling, type safety, async patterns. You get these automatically.

Layer 2, Project-Specific

Generated per-project. Coverage targets, latency SLAs, compliance requirements, domain rules. You customize these.

If Layer 2 conflicts with Layer 1, Layer 2 wins for that specific project. Example: Layer 1 says "TDD for business logic" → Layer 2 says "TDD for ALL code" → Layer 2 applies.

Project Principles

Principles declare what your project believes, non-negotiable commitments about technology, architecture, and quality. They're checked automatically during Steps 1, 2, and 5.

  1. Open Copilot Chat → Agent Mode
  2. Attach .github/prompts/project-principles.prompt.md
  3. Choose your path: A) Interview, B) Starter set for your stack, or C) Discover from codebase
  4. The prompt generates docs/plans/PROJECT-PRINCIPLES.md
Example principles
## Technology Commitments
- PostgreSQL for all persistence, no MongoDB, no SQLite in production
- All services communicate via gRPC, no REST between internal services

## Architecture Commitments
- All data access goes through repositories, no direct SQL in services
- Background jobs use BackgroundService + PeriodicTimer, no Hangfire

## Quality Commitments
- 90% test coverage on business logic, non-negotiable
- No secrets in code, ever. Use IConfiguration + Key Vault

Project Profile

The profile tells the AI how to write code, generated from an interview about your standards:

  1. Attach .github/prompts/project-profile.prompt.md
  2. Answer questions about testing, performance, security, domain rules
  3. The prompt generates .github/instructions/project-profile.instructions.md
Project PrinciplesProject Profile
What it is"We use PostgreSQL, not MongoDB""Use parameterized queries with Dapper"
Who writes itYou (or guided by workshop)Generated from interview
Testing"90% coverage, non-negotiable""Use xUnit with [Fact] and [Theory]"
When it mattersRejects a PR that uses MongoDBTells AI how to write the query

Editing copilot-instructions.md

This is the master config file, loaded every session, for every file. Keep it focused:

  • Project overview, what your app does, who it's for (2–3 sentences)
  • Tech stack, specific versions, frameworks, libraries
  • Architecture, how layers are organized, key patterns
  • Quick commands, build, test, lint, run dev
Keep it under 80 lines. This file loads for every interaction, a 300-line config wastes context budget. Put domain-specific rules in separate instruction files with targeted applyTo patterns.

Writing Custom Instruction Files

Create a new .instructions.md file in .github/instructions/ with YAML frontmatter:

.github/instructions/billing.instructions.md
---
description: Billing domain rules, Stripe integration, invoice generation
applyTo: "**/billing/**,**/invoices/**,**/payments/**"
---

# Billing Domain Rules

- All money amounts stored as `decimal(18,4)`, never `float`
- Use Stripe SDK v45+, never raw HTTP calls
- Every payment mutation must be idempotent (use idempotency keys)
- Invoice PDFs generated async via background service
- All billing events published to `billing.*` topic

When you edit src/billing/InvoiceService.cs, this file loads automatically alongside the universal baseline.

applyTo Pattern Reference

PatternLoads When
'**'ALL files (use sparingly)
'**/*.cs'Any C# file
'**/*.test.ts'TypeScript test files
'**/auth/**'Files in any auth/ directory
'docs/plans/**'Plan documents

Customizing Agents

Agent definitions live in .github/agents/. Each is a Markdown file with YAML frontmatter that declares the agent's role, tool restrictions, and expertise:

Agent definition frontmatter
---
name: "billing-reviewer"
description: "Audit billing code for Stripe compliance and financial accuracy"
tools: ["read_file", "grep_search", "semantic_search"]
---

Agents are read-only, they can search and read but can't edit files. This makes them safe to run as independent auditors. To create a new agent, copy an existing one and modify the expertise section.

AgentRoleHow to invoke
plan-health-auditor Reads run history, memories, bugs, and the active plan to report on slice sizing, gate coverage, missing forbidden actions, and scope contract completeness. Emits a markdown report to .forge/health/latest.md. forge_master_ask({ message: "@plan-health-auditor weekly report" }) or forge_delegate_to_agent with agent: "plan-health-auditor".

Plan Forge ships a built-in plan-health-auditor agent (.github/agents/plan-health-auditor.agent.md) that reads plan files and reports on slice sizing, gate coverage, missing forbidden actions, and scope contract completeness. Invoke it via forge_delegate_to_agent with agent: "plan-health-auditor" or from the Dashboard Agents tab. Read-only; cannot modify plans.

Customizing Skills

Skills are multi-step procedures in .github/skills/*/SKILL.md. Each skill defines steps, validation gates, and expected outputs. Every skill follows the Skill Blueprint format, including Temper Guards, Warning Signs, and Exit Proof sections. To create a custom skill:

  1. Create a directory: .github/skills/my-workflow/
  2. Add SKILL.md with steps, gates, and description
  3. Invoke with /my-workflow in Copilot Chat

Lifecycle Hooks Reference

Plan Forge fires eight lifecycle hooks across three buckets. The Copilot session hooks live in .github/hooks/plan-forge.json and run during every agent turn; the LiveGuard / orchestration hooks are configured in .forge.json#hooks and fire during plan execution; the plan-execution guard is a single Node script that runs ahead of every commit during pforge run-plan. The tables below are the normative reference, every hook the orchestrator knows about, what triggers it, what it can block, and where to configure it. The canonical ordered list of hook names is the HOOK_PASCAL array in pforge-mcp/enums.mjs — both pforge smith (PowerShell + bash) and the orchestrator read from this single source of truth.

Copilot session hooks

Configured in .github/hooks/plan-forge.json. Scripts live in .github/hooks/scripts/ with a .sh POSIX variant and a .ps1 Windows variant; the hook runner picks the right one per host. The default timeout for each hook is shown in the Timeout column, long-running scripts that exceed it are killed and skipped (not failed).

HookTriggerEffectBlocks?TimeoutScript
SessionStart Once at the start of every Copilot session. Injects Project Principles, current phase, and Forbidden Actions from the active plan into the agent's context. Also drains queued OpenBrain entries from .forge/openbrain-queue.jsonl when present. No (advisory) 10 s session-start.{sh,ps1}
PreToolUse Before every agent tool call that writes to the filesystem. Two checks run in series: check-forbidden compares the target path against the active plan's Forbidden Actions block; check-predeploy short-circuits when a slice is about to enter a deploy step. Either can deny the tool call. Yes 5 s / 10 s check-forbidden.{sh,ps1} · check-predeploy.{sh,ps1}
PostToolUse After every agent tool call that wrote to the filesystem. Auto-formats the touched file with the project's formatter (Prettier, dotnet format, Black, etc.) and then runs a quick scan for stub markers (TODO, FIXME, "throw new NotImplementedException", etc.). Stub findings are advisory, they surface in the agent's next turn but do not block. No (advisory) 15 s / 15 s post-edit-format.{sh,ps1} · post-edit-validate.{sh,ps1}
Stop When the agent's turn ends. Warns if files were edited during the turn but no test run was detected. This is the "don't ship untested changes" guard rail. Output appears in the next turn's context as a banner. No (advisory) 10 s stop-check-tests.{sh,ps1}

To disable a session hook for one project, edit .github/hooks/plan-forge.json and remove the entry from the relevant array. To disable a session hook globally, delete or rename the file, missing hook files are silently ignored.

LiveGuard and orchestration hooks

Configured in .forge.json#hooks. These fire during plan execution, the orchestrator invokes the relevant hook function directly (no shell scripts) and reads the matching .forge.json sub-block to pick up project-specific tuning. All hooks are opt-in at the project level and ship with safe defaults.

HookTriggerEffectBlocks?Configure
PreDeploy Before pforge run-plan enters a slice flagged as a deploy step. Runs forge_secret_scan across the configured git range (scanSince, default HEAD~1) plus forge_env_diff to flag missing env keys. Blocks the slice when severity ≥ high. Yes (when blockOnSecrets: true) hooks.preDeploy
PostSlice After every slice commit that matches the conventional-commit pattern (feat|fix|refactor|perf|chore|style|test). Runs forge_drift_report and compares the new drift score against the prior score. Emits a warning when the delta exceeds warnDeltaThreshold (default 10); emits a red banner when the score drops below scoreFloor (default 70). Fires only once per pforge run-plan invocation. No (advisory) hooks.postSlice
PreAgentHandoff On agent-to-agent turn boundaries in multi-agent mode, for example, when the executor agent hands off to the reviewer agent at the end of a slice. Injects LiveGuard context (drift score, MTTR, open incidents) into the next agent's prompt. Also posts a snapshot to OpenClaw when openclaw.endpoint is configured. Skipped when the orchestrator sets PFORGE_QUORUM_TURN=1 during quorum fan-out (one of the documented bypasses, see Appendix U — CLI Internal). No (advisory) hooks.preAgentHandoff
PostRun (invokeAuditor) After every completed pforge run-plan run when hooks.postRun.invokeAuditor.onFailure is true and the run failed, or when everyNRuns is set and the run counter is a multiple of N. Triggers the plan-health auditor agent (A4). The auditor receives cross-run anomaly context from runWatch(mode: "cross-run") and writes its report to .forge/health/latest.md (configurable via forgeMaster.auditor.outputPath). No (advisory) hooks.postRun.invokeAuditor

Example .forge.json snippet for the PostRun auditor hook:

{
  "hooks": {
    "postRun": {
      "invokeAuditor": {
        "onFailure": true,
        "everyNRuns": 10
      }
    }
  }
}

To disable a LiveGuard hook, set the corresponding block in .forge.json to { "enabled": false } or, for finer-grained control, lower its threshold (e.g. blockOnSecrets: false keeps the PreDeploy scan running but downgrades it to advisory). Full schema in Appendix T — hooks.

Plan-execution guard

One special hook lives outside both buckets above: PreCommit.mjs is a Node script in .github/hooks/ that runs synchronously before every commit during pforge run-plan. It now executes an ordered PreCommit chain declared in hooks.preCommit.chain[]. The built-in chain starts with master-branch-reject (refuse interactive commits on master/main) and then diff-classify (run forge_diff_classify against the staged diff). The first non-zero exit aborts the commit.

HookTriggerEffectBlocks?Override
PreCommit Before every git commit during pforge run-plan. Runs each hooks.preCommit.chain[] entry in order. The default chain begins with master-branch-reject (blocks unauthorized commits on master/main) and then diff-classify (blocks high/critical findings from forge_diff_classify). First non-zero exit aborts the chain. Yes Set PFORGE_ALLOW_MASTER_COMMIT=1 for one invocation, or edit .forge.json#hooks.preCommit.chain to add/remove entries. Discouraged, the defaults exist because LiveGuard runs caught several accidental-master-commit incidents in the v3.3.x sweeps.

Hook resolution order

When the orchestrator needs to fire a hook, it looks for configuration in this order, first source that yields a non-empty value wins, with a built-in default at the end:

  1. Environment override, e.g. PFORGE_DISABLE_TEMPERING=1, PFORGE_ALLOW_MASTER_COMMIT=1, PFORGE_QUORUM_TURN=1. See Appendix U — Feature Toggles and CLI Internal for the full list.
  2. Project config, the matching block in .forge.json#hooks for LiveGuard hooks, or the matching entry in .github/hooks/plan-forge.json for session hooks.
  3. Hook script presence, for session hooks, a missing .sh/.ps1 file is treated as "hook disabled" rather than an error. This lets you delete an unused hook script without editing the JSON.
  4. Built-in default, bakes into orchestrator.mjs. The defaults are deliberately conservative: every hook is enabled, every blocking hook actually blocks, every advisory hook actually emits its advisory.

Writing a custom hook

You can add scripts to existing buckets without modifying the orchestrator. For session hooks, drop a new script into .github/hooks/scripts/ and append an entry to the appropriate array in plan-forge.json; the next agent session picks it up. For LiveGuard hooks, the contract is fixed by the orchestrator, you can't add new ones, but you can swap a hook's behavior by wrapping the underlying tool (e.g. point hooks.preDeploy.scanSince at a wider git range, or pre-populate .forge/secret-scan-cache.json with a custom scanner's output).

A representative custom SessionStart hook that injects organization-specific reminders lives in templates/.github/hooks/scripts/session-start.ps1, copy it and edit the $reminders block. The script must emit a single line of JSON in the form {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"..."}} for the agent host to honor the injection.

Configuration Hierarchy

Three levels of configuration, from team-wide to personal:

LevelFileScopeCommitted?
Team.forge.jsonShared project config (presets, models, escalation)Yes
Personalpreferences.jsonIndividual developer preferencesNo (.gitignore)
Editor.vscode/settings.jsonVS Code and Copilot settingsYes (recommended)

Personal preferences override team config for the individual developer. Editor settings control VS Code behavior (agent mode enabled, prompt files, etc.).

For a field-by-field schema of .forge.json, every settable key with type, default, example, and change impact, see Appendix T — .forge.json Reference. For everything that lives outside .forge.json, provider API keys, server ports, orchestrator timing, see Appendix U — Environment Variables Reference.

📄 Full reference: CUSTOMIZATION.md on GitHub