{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "flow plugin — per-repo config",
  "description": "Per-repo configuration consumed by the flow plugin's commands, skills, and subagents. Place at <repo>/.ai/workflows.config.json. Missing file = defaults; missing keys = same.",
  "type": "object",
  "additionalProperties": false,
  "required": ["name"],
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Pointer to this schema (editor support). Stripped at consumption time."
    },
    "name": {
      "type": "string",
      "description": "Short project / product name. Resolves the `<project>` placeholder in commands and file paths (e.g. `.ai/<project>-prd.md` → `.ai/<name>-prd.md`)."
    },
    "language": {
      "type": "string",
      "description": "Primary language used in plans, decisions, and review notes. ISO 639-1 (e.g. `en`, `cs`). Cosmetic — does not change plugin behaviour, only signals the team's preferred tone.",
      "default": "en"
    },
    "theme": {
      "type": "string",
      "enum": ["dark", "light", "agnostic"],
      "description": "Default theme target. `dark` and `light` enable theme-first contrast validation in a11y-rules; `agnostic` checks both.",
      "default": "agnostic"
    },
    "paths": {
      "type": "object",
      "additionalProperties": false,
      "description": "Where to find canonical project context files. Defaults follow `<project>` placeholder convention.",
      "properties": {
        "prd": {
          "type": "string",
          "description": "Project requirements / product brief. Default: `.ai/<name>-prd.md`."
        },
        "designSystem": {
          "type": "string",
          "description": "Design system reference. Default: `.ai/<name>-design-system.md`."
        },
        "codebaseMap": {
          "type": "string",
          "description": "Living codebase snapshot. Default: `.ai/context/codebase-map.md`.",
          "default": ".ai/context/codebase-map.md"
        },
        "designRoot": {
          "type": "string",
          "description": "Root of the design plugin's canvas workspace, relative to repo root. Where flow looks for `.meta.json` sidecars + canvases when bridging to design (`/flow:plan` canvas detection, `/flow:done` handoff sweep, `codebase-intelligence` design-artifacts section). Mirrors the design plugin's own default. Flow reads this directory read-only — it never writes into the design root except the `/flow:done` handoff sweep's meta status update. Default: `.design`.",
          "default": ".design"
        },
        "scenarioGuide": {
          "type": "string",
          "description": "Repo-owned scenario-testing knowledge/deltas file that flow:scenario reads for overrides — device/platform lifecycle, selector conventions, infra-error classification, platform-specific gotchas. Mirrors `integrations.changelog.releaseGuide`'s generic-skill-plus-repo-knowledge-file shape, but optional: unlike the release guide, flow:scenario runs its documented defaults when this file is absent — no project is required to author one. Scaffolded by `maude init`. Default: `.ai/scenario-guide.md`.",
          "default": ".ai/scenario-guide.md"
        }
      }
    },
    "platforms": {
      "type": "array",
      "description": "Platform variants the project ships to. Drives scenario-runner matrix and responsive-rules density mapping.",
      "items": {
        "type": "string",
        "enum": [
          "web-desktop",
          "web-mobile",
          "ios-phone",
          "ios-tablet",
          "android-phone",
          "android-tablet",
          "macos",
          "windows",
          "linux"
        ]
      },
      "default": ["web-desktop"],
      "uniqueItems": true
    },
    "bundleIdPrefix": {
      "type": "string",
      "description": "Native app bundle ID prefix (e.g. `com.acme.myapp`). Used by `/flow:utils-verify` and `/flow:scenario` when launching native builds. Omit for web-only projects."
    },
    "stack": {
      "type": "object",
      "additionalProperties": false,
      "description": "Auto-detected technology stack snapshot. Populated by /flow:init, refreshed by /flow:setup-codebase-map. Flow commands read this for routing decisions (e.g. which test runner to invoke, whether to gate on monorepo workspace builds). Use `unknown` when detection fails — never invent a value.",
      "properties": {
        "language": {
          "type": "string",
          "description": "Primary language: typescript | javascript | python | go | rust | java | kotlin | swift | ruby | php | unknown"
        },
        "framework": {
          "type": "string",
          "description": "Primary UI/app framework: next.js | vite | remix | sveltekit | astro | nuxt | expo | spring-boot | django | rails | express | fastapi | none | unknown"
        },
        "packageManager": {
          "type": "string",
          "description": "pnpm | yarn | npm | bun | cargo | go | pip | poetry | uv | maven | gradle | unknown"
        },
        "buildTool": {
          "type": "string",
          "description": "turbo | nx | lerna | rush | make | bazel | none | unknown"
        },
        "monorepo": {
          "type": "boolean",
          "description": "True if pnpm-workspace.yaml, turbo.json, nx.json, lerna.json, or rush.json is present."
        },
        "ci": {
          "type": "string",
          "description": "github-actions | gitlab-ci | jenkins | azure-devops | circleci | bitbucket | none | unknown"
        },
        "tests": {
          "type": "string",
          "description": "vitest | jest | playwright | cypress | rspec | pytest | go-test | cargo-test | junit | none | unknown"
        },
        "css": {
          "type": "string",
          "description": "tailwind | css-modules | styled-components | emotion | vanilla-extract | sass | plain-css | none | unknown"
        },
        "router": {
          "type": "string",
          "description": "next-app | next-pages | react-router | tanstack-router | expo-router | sveltekit-router | none | unknown"
        }
      }
    },
    "conventions": {
      "type": "object",
      "additionalProperties": false,
      "description": "Team conventions that flow commands enforce or display. Auto-detected where possible (e.g. `commits` from commitlint.config.*); otherwise asked once in /flow:init. Document non-obvious 'why' in `.ai/decisions/` as DDRs.",
      "properties": {
        "branchingModel": {
          "type": "string",
          "enum": ["github-flow", "trunk-based", "gitflow", "release-branch", "other"],
          "description": "How feature branches relate to main. Drives /flow:done's push + PR behavior."
        },
        "commits": {
          "type": "string",
          "enum": ["conventional", "gitmoji", "free-form"],
          "default": "conventional",
          "description": "Commit message style. /flow:done formats commits to match."
        },
        "prohibited": {
          "type": "array",
          "items": { "type": "string" },
          "default": [],
          "description": "Packages, libraries, or APIs the project rejects (e.g. `lodash` because we use native ES; `moment` because we use date-fns). Flow commands flag accidental introduction."
        },
        "proxy": {
          "type": "string",
          "description": "Network constraint that commands must respect (e.g. `GODEBUG=x509negativeserial=1` for orgs with old certs)."
        }
      }
    },
    "boundaries": {
      "type": "object",
      "additionalProperties": false,
      "description": "Integration boundaries the project crosses. debugging-rules and testing-rules treat these as no-mock zones and demand evidence at the seam.",
      "properties": {
        "realtime": { "type": "array", "items": { "type": "string" }, "default": [] },
        "video": { "type": "array", "items": { "type": "string" }, "default": [] },
        "api": { "type": "array", "items": { "type": "string" }, "default": [] },
        "db": { "type": "array", "items": { "type": "string" }, "default": [] },
        "auth": { "type": "array", "items": { "type": "string" }, "default": [] },
        "telemetry": { "type": "array", "items": { "type": "string" }, "default": [] },
        "payments": { "type": "array", "items": { "type": "string" }, "default": [] }
      }
    },
    "motion": {
      "type": "object",
      "additionalProperties": false,
      "description": "Motion duration budgets in milliseconds. motion-rules enforces these as hard ceilings.",
      "properties": {
        "micro": {
          "type": "integer",
          "minimum": 0,
          "default": 300,
          "description": "Hover, focus, tooltip, icon swap."
        },
        "page": {
          "type": "integer",
          "minimum": 0,
          "default": 500,
          "description": "Route transition, modal open."
        },
        "complex": {
          "type": "integer",
          "minimum": 0,
          "default": 1000,
          "description": "Multi-step orchestrations, hero choreography."
        },
        "customPulses": {
          "type": "object",
          "description": "Named long-running ambient animations (e.g. `liveIndicator: 1500`). Exempt from the page/complex caps because they're loops, not transitions.",
          "additionalProperties": { "type": "integer", "minimum": 0 },
          "default": {}
        }
      }
    },
    "responsive": {
      "type": "object",
      "additionalProperties": false,
      "description": "Responsive layout knobs. responsive-rules reads this to enforce density-per-platform.",
      "properties": {
        "approach": {
          "type": "string",
          "enum": ["mobile-first", "desktop-first"],
          "default": "mobile-first"
        },
        "densityMap": {
          "type": "object",
          "description": "Maps each entry in `platforms` to a density preset (`command-center` | `sideline-tool` | `palm-friendly` | `cozy` | `compact` | custom). responsive-rules uses this to flag wrong density per platform.",
          "additionalProperties": { "type": "string" },
          "default": {}
        },
        "breakpoints": {
          "type": "object",
          "description": "Named breakpoint tokens (px). Override only if project differs from defaults.",
          "additionalProperties": { "type": "integer", "minimum": 0 },
          "default": { "sm": 480, "md": 768, "lg": 1024, "xl": 1280, "2xl": 1536 }
        }
      }
    },
    "ux": {
      "type": "object",
      "additionalProperties": false,
      "description": "UX hard targets surfaced to design-system-guard and any project-local UX critic.",
      "properties": {
        "responseTargetMs": {
          "type": "integer",
          "minimum": 0,
          "default": 100,
          "description": "User-perceived response time ceiling for primary interactions."
        },
        "bilingual": {
          "type": "array",
          "items": { "type": "string" },
          "default": [],
          "description": "ISO 639-1 codes the UI ships in (e.g. `[\"cs\", \"en\"]`). Empty = monolingual."
        }
      }
    },
    "orchestration": {
      "type": "object",
      "additionalProperties": false,
      "description": "Opt-OUT, capability-gated bookend debate (diverge→adversarial→research). The debate is ON by default: an absent `orchestration` block is treated as `mode:auto`, so the layer engages everywhere — a live native agent-team (`relay`) when the CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS flag is enabled, else a cheap read-only `reduce` panel. Add this block ONLY to dial it down or off. No hard dependency on experimental Claude Code features (degrades to `reduce` without the flag). See DDR-130.",
      "properties": {
        "mode": {
          "type": "string",
          "enum": ["auto", "reduce", "off"],
          "default": "auto",
          "description": "Opt-out default `auto` (absent block == `auto`). `auto` = live native agent-team (relay) when the CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS capability is detected, else the read-only reduce-pass; gated further by the stakes-gate + short-circuit. `reduce` = always parallel panel + reduce-pass, never a live team. `off` = disable the debate entirely; today's raw single-pass / sum-of-verdicts, byte-for-byte unchanged."
        },
        "bookends": {
          "type": "object",
          "additionalProperties": false,
          "description": "Per-shape opt-out. Debate fires only at the loop's bookends; the middle (execute) stays solo.",
          "properties": {
            "diverge": {
              "type": "object",
              "additionalProperties": false,
              "description": "START / divergent debate (what's BEST, no artifact yet) — e.g. plan, setup-prd, setup-ds.",
              "properties": {
                "enabled": { "type": "boolean", "default": true }
              }
            },
            "adversarial": {
              "type": "object",
              "additionalProperties": false,
              "description": "END / adversarial debate (is it actually safe/done/good, artifact exists) — e.g. validate-security, design critic gate.",
              "properties": {
                "enabled": { "type": "boolean", "default": true }
              }
            },
            "research": {
              "type": "object",
              "additionalProperties": false,
              "description": "RESEARCH debate (what's TRUE — ends when evidence eliminates hypotheses) — e.g. bug-rca, ux-research.",
              "properties": {
                "enabled": { "type": "boolean", "default": true }
              }
            }
          }
        },
        "maxSeats": {
          "type": "integer",
          "minimum": 2,
          "maximum": 4,
          "default": 4,
          "description": "Hard cap on debate seats. The stakes-gate picks where in 2–4 to land (2 for reversible calls, up to 4 for irreversible / high-blast-radius)."
        },
        "escalationCeiling": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "default": 0.5,
          "description": "Observability knob. If the measured short-circuit escalation rate (fraction of bookend runs that escalate from blind openings to a full debate) exceeds this, the protocol warns that the openings aren't independent enough and the escalation should be re-tuned."
        },
        "designTeam": {
          "type": "object",
          "additionalProperties": false,
          "description": "`/design:critic` live-team (relay) tier — opt-out, ON by default. The conflicting critics convene as a native team and revise stances when the experimental flag is on AND the panel produced ≥ `minConflicts` cross-discipline conflicting blockers; otherwise the read-only reduce-pass stands. Set `enabled:false` to keep `/design:critic` on the reduce-pass only.",
          "properties": {
            "enabled": { "type": "boolean", "default": true },
            "minConflicts": { "type": "integer", "minimum": 2, "default": 2 }
          }
        }
      }
    },
    "knowledgeGraph": {
      "type": "object",
      "additionalProperties": false,
      "description": "Opt-OUT, capability-gated knowledge-graph memory backend (kgai). ON by default when the capability exists: an absent `knowledgeGraph` block is treated as `mode:auto`, so when the `kg` CLI is on PATH AND a store is resolvable, flow/design decision reads/writes route through the event-sourced graph instead of `.ai/decisions/` + `.ai/state/STATE.md`. When `kg` is absent, the store is unreachable, or `mode:off`, everything falls back to today's `.ai/` file behavior — no regression, nothing to configure downstream. Mirrors the `orchestration` capability-gate (DDR-130). Design plugin reads the SAME block. See the `kgai-backend` skill for the resolver.",
      "properties": {
        "mode": {
          "type": "string",
          "enum": ["auto", "on", "off"],
          "default": "auto",
          "description": "Opt-out default `auto` (absent block == `auto`). `auto` = active only when `kg` is on PATH AND a store resolves (`kg config` store_root via an approved committed `.kgairc`, this `store` value, or a legacy local `.kgai/store`) — conservative, mirrors `orchestration.mode:auto`; a first-run repo stays on files until the user opts in via onboarding. An unapproved `.kgairc` (`kg config` → pending_approval) counts as NOT resolved until a human runs `kg trust`. `on` = force kgai active (errors surface rather than silently falling back). `off` = disable entirely; today's classic `.ai/decisions/` + STATE.md path, byte-for-byte unchanged."
        },
        "engine": {
          "type": "string",
          "enum": ["kgai"],
          "default": "kgai",
          "description": "Knowledge-graph engine. Reserved for future engines; only `kgai` is implemented."
        },
        "engineVersion": {
          "type": "string",
          "default": "v1.0.0",
          "description": "MINIMUM kgai release the integration targets (a floor, not a pin — the engine installs via the official installer, which self-updates at SessionStart; the manual pinned-download flow is retired). Must be at least `v1.5.1` — the `.kgairc` three-layer config + `kg config`/`kg prompt`/`kg trust` surface. Verify with `kg version`; drift check via `maude kg check-upstream`."
        },
        "store": {
          "type": "string",
          "default": "",
          "description": "Fallback/override store for the `maude kg` verbs. Since kgai v1.5.1 store resolution is primarily the ENGINE's job: a repo with an approved committed `.kgairc` resolves its store via `kg config` (typically a shared parent-folder store like `../.kgai-shared`), and that wins — this value is consulted only when `kg config` yields no store (legacy per-repo setups). The sync remote is set per-STORE with `kg remote` (session layer), never here and never via `kg remote --global`."
        },
        "scope": {
          "type": "object",
          "additionalProperties": false,
          "description": "Cross-repo scope stamped on every write, so one shared store can host many repos/departments and search can bias to the local scope first. Project-agnostic — values are per-repo config, never hardcoded in the plugin.",
          "properties": {
            "repo": {
              "type": "string",
              "description": "This repo's name, stamped as a `repo:<name>` element + `IN_REPO` link on every decision."
            },
            "dept": {
              "type": "string",
              "description": "This repo's department (e.g. dev/marketing/finance/automations), stamped as a `dept:<name>` element + `IN_DEPT` link. Default search bias — reads re-rank `dept:<this>` hits first; `--all-scopes` widens."
            }
          }
        },
        "capture": {
          "type": "object",
          "additionalProperties": false,
          "description": "Which write paths route into the graph when active. All default `true`.",
          "properties": {
            "decisions": {
              "type": "boolean",
              "default": true,
              "description": "Route `/flow:record-ddr` + DDR-worthy design writes to `kg ingest` instead of a `DDR-NNN.md` file."
            },
            "state": {
              "type": "boolean",
              "default": true,
              "description": "Route STATE.md history / pause-resume events to kgai as dated events."
            },
            "auto": {
              "type": "boolean",
              "default": true,
              "description": "Let kgai's own `Stop` hook nudge autonomous decision capture after structural edits (zero added LLM cost). Set `false` to suppress the nudge."
            }
          }
        }
      }
    },
    "security": {
      "type": "object",
      "additionalProperties": false,
      "description": "Security review knobs. security-auditor + ethical-hacker subagents read these. Skip the whole pass with skills.securityRules.enabled = false.",
      "properties": {
        "severityFloor": {
          "type": "string",
          "enum": ["low", "medium", "high", "critical"],
          "default": "medium",
          "description": "Lowest severity that blocks /flow:validate step 6.5 and /flow:validate-security. Findings below this rate as warnings, not blockers."
        },
        "scope": {
          "type": "array",
          "items": { "type": "string", "enum": ["classic", "ai", "supply-chain"] },
          "default": ["classic", "ai", "supply-chain"],
          "description": "Which rule families to evaluate. Drop \"ai\" if the project has no model / MCP surface; drop \"supply-chain\" for projects with no third-party deps."
        },
        "includeAi": {
          "type": "boolean",
          "default": true,
          "description": "Shortcut to enable/disable §B AI-era rules in security-rules skill. When false, ethical-hacker still threat-models classic surface but the mandatory AI/MCP section reads \"N/A — disabled by config\"."
        }
      }
    },
    "skills": {
      "type": "object",
      "additionalProperties": false,
      "description": "Per-skill toggles. Each enforcement skill respects its own `enabled` flag (default `true`).",
      "properties": {
        "motionRules": {
          "type": "object",
          "properties": { "enabled": { "type": "boolean", "default": true } },
          "additionalProperties": false
        },
        "responsiveRules": {
          "type": "object",
          "properties": { "enabled": { "type": "boolean", "default": true } },
          "additionalProperties": false
        },
        "a11yRules": {
          "type": "object",
          "properties": { "enabled": { "type": "boolean", "default": true } },
          "additionalProperties": false
        },
        "testingRules": {
          "type": "object",
          "properties": { "enabled": { "type": "boolean", "default": true } },
          "additionalProperties": false
        },
        "debuggingRules": {
          "type": "object",
          "properties": { "enabled": { "type": "boolean", "default": true } },
          "additionalProperties": false
        },
        "securityRules": {
          "type": "object",
          "properties": { "enabled": { "type": "boolean", "default": true } },
          "additionalProperties": false
        },
        "coverageTrend": {
          "type": "object",
          "description": "Optional coverage-trend warning in /flow:validate. Opt-in — disabled by default, because not every project has a stable coverage signal worth gating on. When enabled, /flow:validate parses the current run's coverage and compares against `.ai/state/coverage-baseline.json`; a drop greater than `warnThresholdPp` percentage points emits a soft warning (never blocks). Baseline is refreshed by /flow:done after a green run.",
          "additionalProperties": false,
          "properties": {
            "enabled": { "type": "boolean", "default": false },
            "warnThresholdPp": {
              "type": "number",
              "description": "Percentage-point drop that triggers the warning. e.g. `1.0` means a 1-point drop (78% → 77%) warns.",
              "default": 1.0,
              "minimum": 0
            },
            "baselineBranch": {
              "type": "string",
              "description": "Branch whose coverage is considered authoritative. /flow:done writes the baseline only when the active branch matches this value (typically `main`).",
              "default": "main"
            }
          }
        }
      }
    },
    "quality": {
      "type": "object",
      "description": "Map of gate name → shell command string. Gate names are free-form (conventional names: `lint`, `format`, `typecheck`, `tests`, `build`). Each command is executed as-is via `eval` by the slash command that consumes the gate. Missing gate → slash command emits a one-line warning and skips (never fabricates a command). v1 deliberately keeps this a flat string map — no per-gate object shape, no `order`, no `scope`. Populate via `maude doctor --fix`, which detects from `package.json` scripts + tool presence. Existing user values are never overwritten — additions only.",
      "additionalProperties": {
        "type": "string",
        "minLength": 1
      }
    },
    "qualityScoped": {
      "type": "object",
      "description": "Scoped (changed-files / changed-packages-only) gate variants for the IMPLEMENTATION inner loop (`/flow:utils-verify`, per-task `/flow:execute`, `/flow:bug-fix`). Same flat shape as `quality` — gate name → shell command string, keys mirror `quality` (conventionally `lint`, `format`, `typecheck`, `tests`). During implementation these run INSTEAD of the repo-wide `quality.*` commands; a missing key means the inner loop defers that gate to `/flow:validate` (it never falls back to the repo-wide command). `/flow:validate` and `/flow:done` ignore this block entirely — the full `quality.*` pipeline remains the single outer gate. Monorepo example: `\"lint\": \"turbo run lint --filter='[origin/main]'\"` — note changed-only `[base]`, NOT dependents-inclusive `...[base]`, which selects nearly the whole monorepo whenever a shared package is touched; dependent breakage is `/flow:validate`'s job.",
      "additionalProperties": {
        "type": "string",
        "minLength": 1
      }
    },
    "integrations": {
      "type": "object",
      "additionalProperties": false,
      "description": "Lightweight pointers to external services the project plugs into. Generic commands read `provider` and decide whether to offer integration actions; project-specific shape (list IDs, custom field names, milestone hierarchies) lives in `defaults`. Document non-obvious conventions in `.ai/decisions/` as DDRs.",
      "properties": {
        "tracker": {
          "type": "object",
          "additionalProperties": false,
          "description": "Issue / task tracker. Used by /flow:done, /flow:bug-rca, /flow:bug-fix and /flow:status to optionally read, create or update tickets. `orbit` additionally routes /flow:plan (find or create the task), /flow:execute (working-state reports) and /flow:done (close-time artifact push, warn-only) through the `orbit-backend` skill.",
          "properties": {
            "provider": {
              "type": "string",
              "enum": [
                "clickup",
                "linear",
                "github",
                "jira",
                "notion",
                "asana",
                "shortcut",
                "orbit",
                "none"
              ],
              "default": "none"
            },
            "mcp": {
              "type": "string",
              "description": "MCP tool name prefix used to call the provider (e.g. `mcp__claude_ai_ClickUp`, `mcp__github`, `mcp__orbit`). Generic commands resolve tool names by appending the operation (`<mcp>_<op>`). Omit if no MCP server is configured — commands will print payloads for manual handling."
            },
            "baseUrl": {
              "type": "string",
              "format": "uri",
              "pattern": "^https?://",
              "description": "Base URL of a self-hosted tracker instance — used by `provider: orbit` (e.g. `https://orbit.example.com`); other providers ignore it. A committed pointer, never a secret: it names the MCP endpoint the project's `.mcp.json` entry targets (`<baseUrl>/api/mcp`) and the task links (`<baseUrl>/t/ORB-123`)."
            },
            "tokenEnv": {
              "type": "string",
              "pattern": "^[A-Z_][A-Z0-9_]*$",
              "default": "ORBIT_MCP_TOKEN",
              "description": "Name of the environment variable holding the per-machine bearer token for the tracker's MCP server — used by `provider: orbit`; other providers ignore it. Only the variable NAME is stored here; the token lives in the user's environment and `.mcp.json` references it as `${<tokenEnv>}`. Flow commands check whether it is set — they never read, print or write its value."
            },
            "defaults": {
              "type": "object",
              "description": "Free-form project-specific defaults (list IDs, milestone names, custom field IDs, status labels, …). Generic commands pass this object through to MCP calls untouched. Schema-free on purpose — your tracker's idiosyncrasies belong here, not in the plugin.",
              "additionalProperties": true
            },
            "artifacts": {
              "type": "object",
              "additionalProperties": false,
              "description": "Where the five workflow artifacts (plan, rca, execution-report, retro, review) LIVE — used by `provider: orbit`; other providers ignore it. Absent → `store: local`, i.e. today's behaviour: the files stay in `.ai/` and orbit receives a copy at close. The full contract is the `flow:orbit-backend` skill, guide 06 — commands never re-derive it.",
              "properties": {
                "store": {
                  "type": "string",
                  "enum": ["local", "orbit", "both"],
                  "default": "local",
                  "description": "`local` — files in `.ai/` are the record (close-time push still applies). `orbit` — orbit is the record: each artifact is pushed the moment it is written and no copy is kept in the repo. `both` — write locally AND push at write time; the migration setting, and the safe way to try `orbit` out."
                },
                "local": {
                  "type": "string",
                  "enum": ["keep", "scratch"],
                  "default": "keep",
                  "description": "Only meaningful with `store: orbit`. `scratch` — the command writes its artifact under `spoolDir` (gitignored), pushes it, and deletes it on a confirmed push; a FAILED push leaves the file there and the next flow command retries it. `keep` — write to the usual `.ai/` path anyway, so a repo mid-migration keeps its history. A file is never deleted before orbit confirms."
                },
                "spoolDir": {
                  "type": "string",
                  "default": ".ai/tmp/orbit-spool",
                  "description": "Directory for scratch artifacts awaiting a confirmed push, relative to repo root. Must be gitignored — `/flow:init` adds it to `.ai/.gitignore`. Nothing else ever writes here and no command reads it except the spool retry."
                },
                "kinds": {
                  "type": "array",
                  "uniqueItems": true,
                  "items": {
                    "type": "string",
                    "enum": ["plan", "rca", "execution-report", "retro", "review"]
                  },
                  "description": "Which artifact kinds this repo routes to orbit. Absent → all five. A kind left out behaves as `store: local` for that kind alone; orbit accepts no other kinds."
                }
              }
            }
          }
        },
        "analytics": {
          "type": "object",
          "additionalProperties": false,
          "description": "Product analytics provider (PostHog, Amplitude, Mixpanel, …). Currently informational — surfaces in scenario reports and DDRs as context.",
          "properties": {
            "provider": { "type": "string", "default": "none" },
            "mcp": { "type": "string" },
            "defaults": { "type": "object", "additionalProperties": true }
          }
        },
        "ci": {
          "type": "object",
          "additionalProperties": false,
          "description": "CI / deploy provider (GitHub Actions, Vercel, Netlify, EAS, …). Used by /flow:validate to know where to look for build status.",
          "properties": {
            "provider": { "type": "string", "default": "none" },
            "mcp": { "type": "string" },
            "defaults": { "type": "object", "additionalProperties": true }
          }
        },
        "design": {
          "type": "object",
          "additionalProperties": false,
          "description": "Design source-of-truth (Figma, Penpot, Sketch, …). Used by /flow:plan when the planning input references design.",
          "properties": {
            "provider": { "type": "string", "default": "none" },
            "mcp": { "type": "string" },
            "defaults": { "type": "object", "additionalProperties": true }
          }
        },
        "changelog": {
          "type": "object",
          "additionalProperties": false,
          "description": "Release-note / changelog tooling. Used by /flow:validate and /flow:done to remind (soft warning) about missing release notes, by /flow:release-changelog to author entries, and by /flow:release to walk the project-owned release runbook. Phase 3 implements the `changesets` provider; other enum values are accepted but treated as no-op until their providers land.",
          "properties": {
            "provider": {
              "type": "string",
              "enum": ["changesets", "git-cliff", "conventional", "custom", "none"],
              "default": "none"
            },
            "scope": {
              "type": "string",
              "description": "Optional package scope for monorepos — e.g. \"@1agh/maude\". Passed to provider commands when they need to target a workspace member."
            },
            "releaseGuide": {
              "type": "string",
              "default": ".ai/release-guide.md",
              "description": "Path (repo-relative) to the project-owned release runbook that /flow:release walks step-by-step. The runbook is plain Markdown — H2 sections become steps, ```bash``` blocks become candidate commands (run only after user confirmation). Scaffolded with a provider-appropriate stub during /flow:init."
            },
            "mcp": { "type": "string" },
            "defaults": { "type": "object", "additionalProperties": true }
          }
        },
        "whatsNew": {
          "type": "object",
          "additionalProperties": false,
          "description": "In-product \"What's New\" feed. When enabled, /flow:done offers to append a user-facing entry for the closed-out feature to `feed`, delegating to the named project skill if set. Generic + opt-in: absent or `enabled:false` → /flow:done skips it silently. Maude wires this to apps/studio/whats-new.json, surfaced in the canvas browser.",
          "properties": {
            "enabled": { "type": "boolean", "default": false },
            "feed": {
              "type": "string",
              "description": "Repo-relative path to the What's New feed JSON the entry is appended to."
            },
            "skill": {
              "type": "string",
              "description": "Optional name of a project skill (in .claude/skills/) that authors the entry; /flow:done invokes it instead of inlining the append."
            }
          }
        }
      }
    }
  }
}
