Optional: Extensions let teams share custom reviewers, prompts, and instruction files as installable packages.
An extension is a folder containing guardrail files that can be installed into any project using the Plan Forge Pipeline. No runtime, no dependencies, no build step — just folders you copy.
.forge/
└── extensions/
├── extensions.json ← manifest of installed extensions
└── <extension-name>/
├── extension.json ← metadata (name, version, author)
├── instructions/ ← .instructions.md files
├── agents/ ← .agent.md files
├── prompts/ ← .prompt.md files
└── README.md ← usage documentation
{
"name": "healthcare-compliance",
"version": "1.0.0",
"description": "HIPAA compliance guardrails and reviewer agent",
"author": "your-org",
"minTemplateVersion": "1.0.0",
"files": {
"instructions": ["hipaa.instructions.md"],
"agents": ["hipaa-reviewer.agent.md"],
"prompts": ["hipaa-checklist.prompt.md"]
}
}
.forge/extensions/<name>/instructions/ → .github/instructions/agents/ → .github/agents/prompts/ → .github/prompts/.\setup.ps1 -InstallExtensions
pforge ext install .forge/extensions/healthcare-compliance
Browse extensions without leaving the terminal:
pforge ext search # Show all available extensions
pforge ext search saas # Filter by keyword
pforge ext search integration # Filter by category
Install directly from the catalog (downloads + installs in one step):
pforge ext add saas-multi-tenancy # Download and install
pforge ext info plan-forge-memory # Show details before installing
The catalog is stored in extensions/catalog.json (fetches from GitHub if not local). Extensions marked with speckit_compatible: true also work as Spec Kit extensions.
Use pforge ext publish to generate a ready-to-submit catalog entry from your extension’s extension.json:
pforge ext publish .forge/extensions/my-extension
This validates your manifest, counts artifacts, and prints the catalog JSON to paste into extensions/catalog.json. It also prints the 4-step submission workflow:
https://github.com/srnichols/plan-forgeextensions/catalog.json — add the generated entryfeat(catalog): add <your-extension-name>Your extension.json must include: name, version, description, author. Optional fields (repository, license, category, tags, speckit_compatible) are inferred with sensible defaults if omitted.
See extensions/PUBLISHING.md for the full submission guide and catalog entry schema.
extension.json with metadata.instructions.md, .agent.md, and/or .prompt.md filesREADME.md explaining what the extension providesSee templates/.forge/extensions/example-extension/ for a starter template.
The following example extensions are included in docs/plans/examples/extensions/:
| Extension | What It Adds | Best For |
|---|---|---|
saas-multi-tenancy |
RLS policies, tenant isolation middleware, cross-tenant prevention | SaaS platforms with row-level security |
azure-infrastructure |
Bicep, Terraform, azd, CAF naming, security guardrails | Any app repo with an infra/ folder |
plan-forge-memory |
Persistent decision capture, project history search, cross-session context | Any project — especially long-running or team-based |
For pure Azure infrastructure repos (no application code), use the
azure-iacpreset instead of the extension. Seepresets/azure-iac/for the full standalone preset.
plan-forge-memory — Persistent Memory via OpenBrainPlan Forge’s 4-session isolation model prevents self-review bias but creates a side effect: each session starts from zero context. The agent that spent 45 minutes resolving a CQRS decision forgets it when the session ends. The next session re-discovers the same answer — or silently contradicts it.
The plan-forge-memory extension connects Plan Forge to OpenBrain — a self-hosted semantic memory server. Once installed, 150+ files across the pipeline automatically search OpenBrain for prior context before acting and capture decisions after completing. Knowledge compounds across phases instead of evaporating between sessions.
What this means in practice:
search_thoughts() returns the 5–10 most relevant prior decisions in ~500 tokens. Without memory, the agent reads 10+ files to reconstruct context (~5,000+ tokens). Multiply that by every slice in every phase.type: "postmortem". Future reviews search those — the agent already knows what to look for in this codebase.Install:
pforge ext install docs/plans/examples/extensions/plan-forge-memory
Requires: OpenBrain running (Docker Compose, Kubernetes, or Azure — 5-minute setup). Zero cost if self-hosted with Ollama.
Extension authors building custom scanners or classifiers for the audit drain loop should note:
pforge-mcp/tempering/scanners/content-audit.mjs for the reference implementation) and register via .forge.json#tempering.scanners.classifier triage lane emits local proposal artifacts to .forge/audits/. Extensions can provide custom classifier logic by implementing the classifier interface referenced by routeFinding() in pforge-mcp/tempering/triage.mjs.classifier-reviewer.agent.md reviews classifier lane proposals. Extension authors can provide domain-specific classifier-reviewer agents for specialized triage (e.g., accessibility findings, performance regressions).See the extension README for the complete integration map, setup guide, and worked examples.
| Channel | How | Best For |
|---|---|---|
| GitHub repo | Clone or download | Open source extensions |
| Git submodule | git submodule add <url> .forge/extensions/<name> |
Team-shared |
| Manual copy | Download and paste | Air-gapped / enterprise |
The extensions.json file in .forge/extensions/ tracks which
extensions are installed. It is updated automatically by the setup script
or CLI, or you can edit it manually.
{
"description": "Installed Plan Forge extensions",
"version": "1.0.0",
"extensions": [
{
"name": "healthcare-compliance",
"version": "1.0.0",
"installedDate": "2026-03-23"
}
]
}
The Tempering subsystem (TEMPER-03 Slice 03.2) introduced an extension surface for API contract scanners. The built-in scanner covers OpenAPI 3.x and GraphQL introspection; additional protocol scanners can be contributed as extensions.
Every scanner extension module must export a single async function:
export async function runScan(ctx) → ScannerResult
ctx shape:
| Field | Type | Description |
|---|---|---|
config |
object |
Loaded tempering config (from .forge/tempering/config.json) |
projectDir |
string |
Absolute path to the project root |
runId |
string |
Current run ID (e.g. run-2026-04-19T…) |
sliceRef |
{plan, slice} \| null |
Optional plan+slice context |
importFn |
Function |
Dynamic import for optional dependencies |
now |
Function |
Monotonic clock (() → number) |
env |
object |
process.env-shaped environment map |
Return value (ScannerResult):
{
scanner: string, // e.g. "grpc-proto"
verdict: "pass" | "fail" | "error" | "skipped" | "budget-exceeded",
pass: number,
fail: number,
skipped: number,
durationMs: number,
violations?: Array<{ path?, method?, expected?, actual?, reason: string }>,
reason?: string, // when verdict is "skipped"
details?: object, // scanner-specific metadata
}
Each scanner extension should register its config under config.scanners.<name>:
{
"scanners": {
"grpc-proto": {
"enabled": true,
"protoPath": "proto/",
"baseUrl": "localhost:50051"
}
}
}
Scanners write artifacts to .forge/tempering/artifacts/<runId>/<scanner-name>/. Use the ensureScannerArtifactDir() helper from tempering/artifacts.mjs.
config.scanners.<name>.allowProduction === true. Use looksLikeProduction() from ui-playwright.mjs.X-Tempering-Scan: true header: All HTTP requests must include this header so servers can identify scanner traffic.hardDeadline between operations and return verdict: "budget-exceeded" if exceeded.The following scanner slots are defined in extensions/catalog.json under opportunities[]:
| Name | Protocol | Status |
|---|---|---|
tempering-grpc |
gRPC proto contract scanner | Stub |
tempering-trpc |
tRPC router type-check scanner | Stub |
tempering-asyncapi |
AsyncAPI event contract scanner | Stub |
These are not installable via pforge ext add — they exist as contribution placeholders for the community.
The Tempering subsystem (TEMPER-06 Slice 06.2) introduced an extension surface for bug-tracking adapters. The built-in adapter covers GitHub Issues; additional provider adapters can be contributed as extensions.
Contract frozen at v2.47.0. The 4-function adapter signature (
registerBug,updateBugStatus,commentValidatedFix,syncStatusFromProvider) is now considered stable. New optional fields (linkedFixPlan,validationAttempts[]) added in v2.47.0 may appear on thebugparameter but do not change the function signatures.
Every bug-adapter extension module must be placed at:
.forge/extensions/<provider>/tempering-bug-adapter.mjs
The module must export 4 async functions — all must return { provider: string, ok: boolean, ... } and never throw:
export async function registerBug(bug, config, opts)
// → { provider, ok, issueNumber?, url?, error?, warnings? }
export async function updateBugStatus(bug, config, opts)
// → { provider, ok, commentId?, url?, error? }
export async function commentValidatedFix(bug, config, opts)
// → { provider, ok, commentId?, url?, error? }
export async function syncStatusFromProvider(bugId, config, opts)
// → { provider, ok, status?, labels?, error? }
bug — The full bug record from .forge/bugs/<id>.json:
| Field | Type | Description |
|---|---|---|
bugId |
string |
e.g. bug-2026-04-19-001 |
fingerprint |
string |
SHA-1 dedup fingerprint |
scanner |
string |
Scanner that discovered the bug |
severity |
string |
critical | high | medium | low |
status |
string |
open | in-fix | fixed | wont-fix | duplicate |
classification |
string |
real-bug | infra | needs-human-review | unknown |
evidence |
object |
{ testName, assertionMessage, stackTrace, ... } |
affectedFiles |
string[] |
Source files affected |
externalRef |
object? |
{ provider, issueNumber, url, syncedAt } — set after first sync |
linkedFixPlan |
string? |
Relative path to auto-generated fix plan (v2.47.0+) |
validationAttempts |
array? |
[{ at, scanners, result, details }] — validation history (v2.47.0+) |
config — Loaded from .forge.json or .forge/tempering/config.json:
| Field | Type | Description |
|---|---|---|
bugRegistry.integration |
string |
Provider name: "github", "jsonl", or extension name |
bugRegistry.autoCreateIssues |
boolean |
Must be true for registerBug to fire |
bugRegistry.labelPrefix |
string |
Label prefix (default: "tempering") |
bugRegistry.githubRepo |
string? |
Explicit owner/repo override |
opts — Dependency injection bag:
| Field | Type | Description |
|---|---|---|
cwd |
string |
Project root directory |
fetch |
Function? |
Override for globalThis.fetch |
execSync |
Function? |
Override for child_process.execSync |
{ provider: "<your-provider>", ok: boolean, ... }.{ provider, ok: false, error: "<ERROR_CODE>" }.Set the integration provider in .forge.json:
{
"bugRegistry": {
"integration": "<provider>",
"autoCreateIssues": true,
"labelPrefix": "tempering"
}
}
Where <provider> matches your extension folder name under .forge/extensions/.
The following bug-adapter slots are defined in extensions/catalog.json under opportunities[]:
| Name | Provider | Status |
|---|---|---|
tempering-bug-gitlab |
GitLab Issues | Stub |
tempering-bug-azure-boards |
Azure DevOps Boards | Stub |
tempering-bug-jira-cloud |
Jira Cloud | Stub |
tempering-bug-linear |
Linear | Stub |
tempering-bug-jira-onprem |
On-prem Jira Server/DC | Stub |
These are not installable via pforge ext add — they exist as contribution placeholders for the community.