# Campaign Spec A campaign is the compiled, structured form of a generic intent. The user speaks a sentence; the skill turns it into this spec, echoes it back for confirmation, and executes _the spec_ — never the loose sentence. The spec is what makes the pilot, idempotency, chunking, and reporting possible. This file defines the schema only. It is deliberately abstract — no named change appears here. For fully-worked instances, read `../examples/`. ## Fields ### `intent` One sentence, in the user's words, restated and confirmed. This is the single source of truth for what every target should end up with. If the pilot diff does something the intent does not describe, the recipe — not the intent — is wrong. ### `scope` `multi-repo` or `monorepo`. - `multi-repo` — targets are repositories across the Bitwarden ecosystem. Each target is cloned and changed in isolation; each yields its own branch and PR. - `monorepo` — targets are projects/packages/workspaces inside one repository. The fan-out happens across paths in a single clone; the campaign yields one branch and PR per project, or one grouped PR, as the PR spec dictates. ### `target_selector` How candidates are enumerated and filtered. Two parts: - `enumerate` — the command(s) that list all candidate targets (repos in the org, or project paths in the monorepo). - `applicability_filter` — the _signal_ that marks a candidate as relevant, plus how it is detected. A candidate that lacks the signal is `skipped-not-applicable`, never `failed`. Both are built from the patterns in `finding-targets.md`. The selector resolves to an explicit, finite list that is shown to the user before anything is touched. ### `recipe` The per-target unit of work. Has a `type` and the work itself: - `type` — `deterministic` | `agentic` (see SKILL.md → Recipe types). - `body` — for deterministic: the exact edit/script and its parameters. For agentic: the scoped sub-agent prompt, which names inline the minimal set of tools the sub-agent may use (expressed as prose within the prompt — there is no separate allowlist field). - `idempotency` — the condition under which the recipe is already satisfied and must no-op. Required. Without it the campaign cannot be safely remediated or re-run. ### `validation` The per-target gate that must pass before a commit is made. Defined by what the target's own repo specifies (its CLAUDE.md build/lint/test commands) plus `Skill(perform-preflight)`. A target whose validation fails is recorded as `failed` and left without a PR. ### `pr_spec` The shape of the change as it is delivered. Confirmed once on the pilot, then replicated: - `branch` — a deterministic branch name template (same input → same branch, so re-runs are no-ops). - `title` — follows `Skill(committing-changes)` / `Skill(labeling-changes)` format (`[TICKET] : `), so CI applies the right `t:` label. `[TICKET]` is a placeholder — substitute a real tracking-issue key, never the literal word. - `body` — fills the target repo's `.github/PULL_REQUEST_TEMPLATE.md` per `Skill(creating-pull-request)`. - `labels` — the `ai-review` choice and any others, confirmed at pilot. - `draft` — `true` by default. ### `safety_policy` The guardrails for this campaign. Defaults live in `safety-and-self-checks.md`; the spec records any deviations explicitly: - `max_targets_per_run` — default 10. Caps concurrency **per chunk**, not the whole campaign; the total fan-out (count + scope) is confirmed with the user before the first chunk (see `safety-and-self-checks.md`). - `destructive` — whether the recipe removes/rewrites; if true, a reference-check pre-step is required. - `dry_run` — if true, do everything through validation and the secrets-scan, then stop before commit, push, and PR. - `pilot` — `required` by default. `--no-pilot` may downgrade it only for deterministic, fully-reviewable recipes; for agentic recipes `--no-pilot` is refused. Any downgrade is logged. ## Abstract instance A spec is illustrated here with placeholders only — `<…>` marks where a real campaign substitutes its specifics. ```yaml intent: "" scope: multi-repo # or monorepo target_selector: enumerate: "" applicability_filter: signal: "" detect: "" recipe: type: deterministic # or agentic body: "" idempotency: "" validation: " + Skill(perform-preflight)" pr_spec: branch: "" title: "[] : " body: "" labels: [""] draft: true safety_policy: max_targets_per_run: 10 destructive: false dry_run: false pilot: required ```