diff --git a/CHANGELOG.md b/CHANGELOG.md
index cebc9020..3d7cea40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
## [Unreleased]
+### Added
+- **`/v:onboard` Operations/Deployment dimension** (#4, @khymerao). New `detect-ops` subcommand
+ inventories CI/CD + container + deploy signals and produces a cited, gated
+ `docs/superpowers/architecture/operations.md` — a layer previously dropped from the generated KB.
+ The detector is a common-case accelerator, never a verdict: an empty scan reports "no signals
+ found" (`no-signals`) and the HUMAN GATE surfaces it as an open question ("point me at your
+ deployer"), never a confident "no ops". Rebased onto v3.5.1.
+
## [3.5.1] - 2026-09-04
### Fixed — contributed
diff --git a/commands/v-onboard.md b/commands/v-onboard.md
index 5d8cee46..17711084 100644
--- a/commands/v-onboard.md
+++ b/commands/v-onboard.md
@@ -39,6 +39,8 @@ indexing is [`/v:memory-refresh`](v-memory-refresh.md).
repo, and `rules-lint` must exit 0 before those files are committed. The body grammar allows only
one short H1, blank lines and CITED items/paragraphs — fenced and indented code are refused — so an
uncited sentence cannot ride along. `rules-plan` proposes areas; it never writes a rule.
+7. **`operations.md` only when `detect-ops` found ops signals (or the maintainer pointed at a bespoke
+ deployer), and confirmed at the gate.** An empty scan is an open question, never a "no ops" verdict.
When the pipeline (or refresh) finishes, report what was written, what the doctor recommended
(advisory — including **MCP / external-tool recommendations** via `recommend-mcp`: CLI-over-MCP so a
diff --git a/docs/superpowers/plans/2026-07-17-v-onboard-operations-dimension.md b/docs/superpowers/plans/2026-07-17-v-onboard-operations-dimension.md
new file mode 100644
index 00000000..ed72ebaf
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-17-v-onboard-operations-dimension.md
@@ -0,0 +1,359 @@
+# /v:onboard Operations/Deployment Dimension — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add an explicit Operations/Deployment coverage dimension to `/v:onboard` so CI/CD, container, and deploy files produce a cited `docs/superpowers/architecture/operations.md`.
+
+**Architecture:** A deterministic `detect-ops` subcommand (mirroring `detect-ui`) inventories CI/CD + container + deploy files. The `onboarding.md` authority doc grows a 5th claim type (`operations`), a conditional `operations.md` doc section, and an explicit per-artifact confirm at the HUMAN GATE. No `verify-citations`/claims-schema change — the claim `type` field is free-form there.
+
+**Tech Stack:** Python 3 stdlib (`os`, `argparse`, `json`), Markdown authority docs. The script has a built-in `--selftest` harness (no pytest).
+
+## Global Constraints
+
+- **No new runtime deps** — `compound-v-onboard.py` is pure Python 3 stdlib. Copy that constraint verbatim.
+- **`detect_ops` must work on a non-git temp dir** — the `--selftest` harness runs against `tempfile.mkdtemp()` trees that are not git repos, so detection walks the filesystem (excluding `VENDOR_DIRS`), not `git ls-files`.
+- **No `verify-citations` / claims-schema change** — the `type` field is free-form data in that gate; adding the `operations` value touches prose only.
+- **Onboarding pipeline only** — do not touch the brainstorm pre-flights (`code-archaeologist` / `domain-expert` / `doc-validator`).
+- **The ask lives at the HUMAN GATE (§6)**, not DETECT — `operations.md` is generated then confirmed per-artifact; an unattended/auto-approve run approves it with no new code path.
+- **Commit after each task.** Work is on branch `feat/v-onboard-operations-dimension` in the plugin repo (`/Users/koristuvac/.claude/plugins/marketplaces/procoders`).
+
+---
+
+### Task 1: `detect-ops` subcommand + selftest
+
+**Files:**
+- Modify: `scripts/compound-v-onboard.py` (add `_ops_category` + `detect_ops` after `detect_ui` at line 217; add selftest checks after the `detect_ui` checks near line 893; add parser after line 1155; add `main()` branch after line 1200)
+
+**Interfaces:**
+- Consumes: `VENDOR_DIRS` (line 21), the selftest `check(name, cond)` helper (line 807).
+- Produces:
+ - `detect_ops(repo: str) -> dict` returning `{"present": bool, "ci_cd": [str], "containers": [str], "deploy": [str]}` (paths repo-relative, `/`-separated, sorted).
+ - CLI `detect-ops --repo
[--json]`: prints `ops`/`no-ops` by default; the JSON dict with `--json`. Exit 0.
+
+- [ ] **Step 1: Write the failing selftest checks**
+
+Insert immediately **after** line 893 (`check("detect_ui false on bare", ...)`):
+
+```python
+ # detect_ops: CI/CD + container + deploy inventory (walks fs, not git — selftest dirs aren't repos).
+ d5b = tempfile.mkdtemp()
+ try:
+ os.makedirs(os.path.join(d5b, ".github", "workflows"))
+ with open(os.path.join(d5b, ".github", "workflows", "ci.yml"), "w") as fh: fh.write("on: push\n")
+ with open(os.path.join(d5b, "Dockerfile"), "w") as fh: fh.write("FROM alpine\n")
+ with open(os.path.join(d5b, "fly.toml"), "w") as fh: fh.write("app='x'\n")
+ r_ops = detect_ops(d5b)
+ check("detect_ops present true on ci+docker", r_ops["present"] is True)
+ check("detect_ops finds ci_cd workflow", ".github/workflows/ci.yml" in r_ops["ci_cd"])
+ check("detect_ops finds container Dockerfile", "Dockerfile" in r_ops["containers"])
+ check("detect_ops finds deploy fly.toml", "fly.toml" in r_ops["deploy"])
+ finally:
+ shutil.rmtree(d5b, ignore_errors=True)
+ check("detect_ops present false on bare", detect_ops(tempfile.mkdtemp())["present"] is False)
+```
+
+- [ ] **Step 2: Run selftest to verify it fails**
+
+Run: `python3 scripts/compound-v-onboard.py --selftest`
+Expected: FAIL/traceback — `NameError: name 'detect_ops' is not defined` (function not yet added).
+
+- [ ] **Step 3: Add `_ops_category` + `detect_ops`**
+
+Insert immediately **after** line 217 (the closing `return False` of `detect_ui`, before the blank line preceding `_design_result_ok`):
+
+```python
+
+
+def _ops_category(rel: str):
+ """Classify a repo-relative path as an operations file, or None. Deterministic signal set;
+ k8s detection is a filename/dir heuristic (documented as such — it cannot see manifest content)."""
+ low = rel.lower()
+ base = low.rsplit("/", 1)[-1]
+ # --- CI/CD ---
+ if low.startswith(".github/workflows/") and low.endswith((".yml", ".yaml")):
+ return "ci_cd"
+ if low in (".gitlab-ci.yml", ".circleci/config.yml", ".travis.yml",
+ "azure-pipelines.yml", "bitbucket-pipelines.yml"):
+ return "ci_cd"
+ if base == "jenkinsfile":
+ return "ci_cd"
+ # --- containers / infra ---
+ if base == "dockerfile" or base.startswith("dockerfile."):
+ return "containers"
+ if (base.startswith("docker-compose") or base.startswith("compose.")) \
+ and low.endswith((".yml", ".yaml")):
+ return "containers"
+ if low.endswith((".tf", ".tfvars")):
+ return "containers"
+ if base in ("kustomization.yaml", "chart.yaml") or low.startswith("k8s/") or "/k8s/" in low:
+ return "containers"
+ # --- deploy / PaaS ---
+ if base in ("procfile", "fly.toml", "vercel.json", "netlify.toml",
+ "render.yaml", "serverless.yml", "app.yaml"):
+ return "deploy"
+ if base.startswith("deploy") and base.endswith(".sh"):
+ return "deploy"
+ return None
+
+
+def detect_ops(repo: str) -> dict:
+ """Inventory CI/CD + container/infra + deploy files. Walks the filesystem (excluding VENDOR_DIRS)
+ so it works on non-git trees too. `present` is True iff any category matched."""
+ found = {"ci_cd": [], "containers": [], "deploy": []}
+ for dirpath, dirnames, filenames in os.walk(repo):
+ dirnames[:] = [d for d in dirnames if d not in VENDOR_DIRS]
+ for fn in filenames:
+ rel = os.path.relpath(os.path.join(dirpath, fn), repo).replace(os.sep, "/")
+ cat = _ops_category(rel)
+ if cat:
+ found[cat].append(rel)
+ for k in ("ci_cd", "containers", "deploy"):
+ found[k].sort()
+ found["present"] = any(found[k] for k in ("ci_cd", "containers", "deploy"))
+ return found
+```
+
+- [ ] **Step 4: Run selftest to verify detection passes**
+
+Run: `python3 scripts/compound-v-onboard.py --selftest`
+Expected: all `detect_ops` checks PASS; the harness still ends green (the CLI branch is exercised in Step 6, so the whole suite passing is fine now).
+
+- [ ] **Step 5: Add the CLI parser**
+
+Insert immediately **after** line 1155 (`sp = sub.add_parser("detect-ui"); sp.add_argument("--repo", default=".")`):
+
+```python
+ sp = sub.add_parser("detect-ops"); sp.add_argument("--repo", default="."); sp.add_argument("--json", action="store_true")
+```
+
+- [ ] **Step 6: Add the `main()` dispatch branch**
+
+Insert immediately **after** line 1200 (the `return 0` closing the `detect-ui` branch):
+
+```python
+ if args.cmd == "detect-ops":
+ result = detect_ops(os.path.abspath(args.repo))
+ if args.json:
+ print(json.dumps(result, indent=2))
+ else:
+ print("ops" if result["present"] else "no-ops")
+ return 0
+```
+
+- [ ] **Step 7: Verify the CLI end-to-end**
+
+Run:
+```bash
+python3 scripts/compound-v-onboard.py detect-ops --repo . --json
+python3 scripts/compound-v-onboard.py detect-ops --repo .
+python3 scripts/compound-v-onboard.py --selftest
+```
+Expected: the `--json` call prints a dict with `"present": true` and this plugin repo's own `.github/workflows/*` under `ci_cd`; the plain call prints `ops`; `--selftest` prints its summary line and exits 0.
+
+- [ ] **Step 8: Commit**
+
+```bash
+git add scripts/compound-v-onboard.py
+git commit -m "feat(v-onboard): add deterministic detect-ops subcommand + selftest"
+```
+
+---
+
+### Task 2: `onboarding.md` — DETECT, EXTRACT, operations.md section, GATE, WRITE, refresh
+
+**Files:**
+- Modify: `skills/compound-v/onboarding.md` (§1 DETECT bullet; §3 EXTRACT type enum; new "operations.md" subsection under the CONVENTIONS/DESIGN section; §6 GATE confirm; §7 WRITE surface; §Refresh manifest note)
+
+**Interfaces:**
+- Consumes: `detect_ops` CLI from Task 1 (`python3 scripts/compound-v-onboard.py detect-ops --repo . --json`).
+- Produces: authority-doc contract that a conditional `docs/superpowers/architecture/operations.md` is generated (gated on `detect-ops`), confirmed at §6, and written/refreshed like any cited arch doc.
+
+- [ ] **Step 1: Add the DETECT Operations/Deployment bullet (§1)**
+
+In `skills/compound-v/onboarding.md`, in the `### 1. DETECT` list, insert a new bullet immediately **after** the UI-presence bullet (the one ending "…decides whether the DESIGN.md branch runs (step 9 / §DESIGN below)."):
+
+```markdown
+- **Operations / Deployment presence** via `python3 scripts/compound-v-onboard.py detect-ops
+ --repo . --json` → `{present, ci_cd[], containers[], deploy[]}`. Inventories CI/CD
+ (`.github/workflows/*`, `.gitlab-ci.yml`, `.circleci/config.yml`, `Jenkinsfile`,
+ `azure-pipelines.yml`, `.travis.yml`, `bitbucket-pipelines.yml`), container/infra
+ (`Dockerfile*`, `docker-compose*`/`compose.*`, `*.tf`/`*.tfvars`, and k8s heuristics —
+ `k8s/`, `kustomization.yaml`, Helm `Chart.yaml`), and deploy/PaaS (`Procfile`, `fly.toml`,
+ `vercel.json`, `netlify.toml`, `render.yaml`, `serverless.yml`, `app.yaml`, `deploy*.sh`).
+ Silent inventory like `detect-ui` — the *include-it?* ask lives at the GATE (§6), not here.
+ `present: true` is what gates the operations.md branch (§operations.md below). k8s detection
+ is a filename/dir heuristic and is stated as such at the gate.
+```
+
+- [ ] **Step 2: Extend the EXTRACT claim-type enum (§3)**
+
+In `### 3. EXTRACT`, change the `type` enum line. Find:
+
+```markdown
+`type`
+(`architecture | business-logic | tech-context | convention`), `citations[{path,startLine,endLine}]`,
+```
+
+Replace with:
+
+```markdown
+`type`
+(`architecture | business-logic | tech-context | convention | operations`), `citations[{path,startLine,endLine}]`,
+```
+
+Then, immediately **after** the paragraph that ends "…and `target_doc_section`." add:
+
+```markdown
+`operations` claims (CI/CD, container topology, deploy target, runbook pointers) target
+`operations.md` and are emitted **only when DETECT's `detect-ops` reported `present: true`**. The
+load-bearing rule still bites: a deploy-secret path, a production/branch deploy gate, or a
+fail-closed CI check is **load-bearing** (`security` / `fail-closed`) and blocks on unsupported
+per the two-tier gate (§4) like any other load-bearing claim. `type` is free-form to
+`verify-citations`, so this adds no schema change.
+```
+
+- [ ] **Step 3: Add the operations.md doc section**
+
+In the `## CONVENTIONS.md and DESIGN.md` section, append a new bullet at the end of the list (after the `DESIGN.md` bullet and its WCAG sub-paragraph):
+
+```markdown
+- **`operations.md`** (`docs/superpowers/architecture/`, ops repos) is generated **only when
+ `detect-ops` reported `present: true`.** On a repo with no CI/CD, container, or deploy files it
+ is **skipped** (verify this negative path on a non-ops dogfood, mirroring the DESIGN.md negative
+ path). Read-then-cite from the real workflow / Docker / compose / Terraform / deploy files
+ DETECT inventoried — never from the model's prior. Cover: container topology (services, ports,
+ volumes), CI/CD stages (build → test → deploy triggers and branch/environment gates), the deploy
+ target + production domain, and runbook pointers. **No credential is ever extracted into the
+ doc** — the blocking `scan-output` gate (§7) refuses a generated file that contains one, and a
+ deploy-secret reference is documented by *path*, not value.
+```
+
+- [ ] **Step 4: Add the §6 GATE per-artifact confirm**
+
+In `### 6. HUMAN GATE`, immediately **after** the paragraph that ends "…**Nothing is written before explicit approval** — no auto-apply, ever." add:
+
+```markdown
+When `detect-ops` reported `present: true`, present `operations.md` as its **own explicit
+per-artifact confirm**, framed with the detected inventory: *"DevOps/deployment tooling detected —
+`` — include `operations.md`?"* Declining drops the doc
+and writes nothing for it; this is the *"ask the user whether to take DevOps into account"* decision.
+A fully autonomous / unattended run (auto-approve / `--permission-mode dontAsk` — today the headless
+marathon, or any future autonomous onboarding cycle) approves it like every other artifact, so ops
+is taken into account **without asking** — no separate code path is needed.
+```
+
+- [ ] **Step 5: Add operations.md to the §7 WRITE surface**
+
+In `### 7. WRITE`, in the "Write **only** what was approved" paragraph, find the write-surface list:
+
+```markdown
+`docs/superpowers/architecture/*`, root `CONVENTIONS.md`, root `DESIGN.md` (UI repos), `AGENTS.md`,
+```
+
+The glob `docs/superpowers/architecture/*` already covers `operations.md`; add an explicit parenthetical so the conditional is unmissable. Replace that line with:
+
+```markdown
+`docs/superpowers/architecture/*` (including `operations.md` — **only when the ops gate was approved**,
+§6), root `CONVENTIONS.md`, root `DESIGN.md` (UI repos), `AGENTS.md`,
+```
+
+- [ ] **Step 6: Note operations.md in the Refresh/manifest contract**
+
+In the `## Refresh — cited-evidence staleness` section, at the end of the first bullet (the `--refresh` re-extract bullet), append one sentence:
+
+```markdown
+ `operations.md` is a normal cited arch doc, so it rides this same `.onboard-manifest.json`
+ cited-evidence staleness machinery with no new gate.
+```
+
+- [ ] **Step 7: Verify the edits landed coherently**
+
+Run:
+```bash
+grep -n "detect-ops" skills/compound-v/onboarding.md
+grep -n "operations.md" skills/compound-v/onboarding.md
+grep -n "convention | operations" skills/compound-v/onboarding.md
+```
+Expected: `detect-ops` appears in §1 DETECT (and §operations.md); `operations.md` appears in §1, EXTRACT, the doc section, §6, §7, and Refresh; the enum line shows the new `operations` value. Read the five edited regions once to confirm no dangling references and that the DESIGN.md-parallel phrasing reads cleanly.
+
+- [ ] **Step 8: Commit**
+
+```bash
+git add skills/compound-v/onboarding.md
+git commit -m "docs(v-onboard): wire operations.md dimension into onboarding pipeline"
+```
+
+---
+
+### Task 3: Spec artifacts table + conditional-fourth prose
+
+**Files:**
+- Modify: `docs/superpowers/specs/2026-06-30-v-onboard-design.md` (artifacts table near line 51; "three architecture files" prose near line 57)
+
+**Interfaces:**
+- Consumes: nothing (documentation-of-record update).
+- Produces: the base spec's artifacts table lists `operations.md` and its prose names it the conditional fourth arch doc — keeping the design of record consistent with the shipped behavior.
+
+- [ ] **Step 1: Add the artifacts-table row**
+
+In `docs/superpowers/specs/2026-06-30-v-onboard-design.md`, find the table row (line 51):
+
+```markdown
+| `architecture.md`, `business-logic.md`, `tech-context.md` | `docs/superpowers/architecture/` | always | citation hybrid (§7) |
+```
+
+Insert a new row immediately **after** it:
+
+```markdown
+| `operations.md` | `docs/superpowers/architecture/` | ops files present, confirmed at gate | citation hybrid (§7) |
+```
+
+- [ ] **Step 2: Update the "three architecture files" prose**
+
+Find (line 57–59):
+
+```markdown
+The three `architecture/` files follow Cline's Memory Bank model (systemPatterns,
+productContext, techContext), trimmed to the durable set. The fast-changing
+`progress.md`/`activeContext.md` are **out of v1**.
+```
+
+Replace with:
+
+```markdown
+The three always-on `architecture/` files follow Cline's Memory Bank model (systemPatterns,
+productContext, techContext), trimmed to the durable set, plus a **conditional fourth
+`operations.md`** — generated only when `detect-ops` finds CI/CD / container / deploy files and
+the maintainer confirms it at the gate. The fast-changing `progress.md`/`activeContext.md` are
+**out of v1**.
+```
+
+- [ ] **Step 3: Verify**
+
+Run:
+```bash
+grep -n "operations.md" docs/superpowers/specs/2026-06-30-v-onboard-design.md
+```
+Expected: two hits — the table row and the prose. Read both to confirm the table stays aligned and the prose reads cleanly.
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add docs/superpowers/specs/2026-06-30-v-onboard-design.md
+git commit -m "docs(v-onboard): record operations.md as conditional fourth arch doc"
+```
+
+---
+
+## Self-Review
+
+**Spec coverage** (against `2026-07-17-v-onboard-operations-dimension-design.md`):
+- §4.1 `detect-ops` (signals, dict shape, CLI, selftest) → Task 1. ✓
+- §4.2 DETECT bullet → T2 S1; EXTRACT 5th type → T2 S2; operations.md section → T2 S3; §6 gate confirm → T2 S4; §7 write surface → T2 S5; refresh note → T2 S6. ✓
+- §4.3 spec table row + conditional-fourth prose → Task 3. ✓
+- §2 "no `verify-citations` change / no pre-flight change" → honored (no such steps; stated in Global Constraints). ✓
+- §6 verification (selftest green, `--json` on ops repo, negative path documented) → T1 S7, and the negative path is written into the onboarding.md operations.md section (T2 S3). ✓
+
+**Placeholder scan:** no TBD/TODO; every code step shows full code; doc steps show exact find/replace text. ✓
+
+**Type consistency:** `detect_ops` returns `{"present", "ci_cd", "containers", "deploy"}` in Task 1 and every later reference (T1 selftest, T1 CLI, T2 DETECT bullet, T2 gate confirm) uses those exact keys and the `ops`/`no-ops` CLI strings. `_ops_category` returns exactly `"ci_cd" | "containers" | "deploy" | None`, matching the `found` dict keys. ✓
diff --git a/docs/superpowers/specs/2026-06-30-v-onboard-design.md b/docs/superpowers/specs/2026-06-30-v-onboard-design.md
index edfcc4e5..5a27ee90 100644
--- a/docs/superpowers/specs/2026-06-30-v-onboard-design.md
+++ b/docs/superpowers/specs/2026-06-30-v-onboard-design.md
@@ -49,14 +49,18 @@ it in would change that command's character. They stay composable: `/v:init`'s c
| File | Location | When | Verification |
|---|---|---|---|
| `architecture.md`, `business-logic.md`, `tech-context.md` | `docs/superpowers/architecture/` | always | citation hybrid (§7) |
+| `operations.md` | `docs/superpowers/architecture/` | ops signals found (or maintainer-pointed), confirmed at gate | citation hybrid (§7) |
| `CONVENTIONS.md` | repo root | code present | derived from real config evidence |
| `DESIGN.md` (Google format) | repo root | UI repo only | `@google/design.md lint` (§8 caveat) |
| `AGENTS.md` (primary, confirmable) + thin `CLAUDE.md` (`@AGENTS.md`) | repo root | always | detect-and-bridge (§6) |
| `.onboard-manifest.json` (cited files + content hashes) | `docs/superpowers/architecture/` | always | machinery — **out of the index** |
-The three `architecture/` files follow Cline's Memory Bank model (systemPatterns,
-productContext, techContext), trimmed to the durable set. The fast-changing
-`progress.md`/`activeContext.md` are **out of v1**.
+The three always-on `architecture/` files follow Cline's Memory Bank model (systemPatterns,
+productContext, techContext), trimmed to the durable set, plus a **conditional fourth
+`operations.md`** — generated only when `detect-ops` finds CI/CD / container / deploy signals (or the
+maintainer points at a bespoke deployer) and confirms it at the gate; an empty scan is an open
+question, never a "no ops" verdict. The fast-changing `progress.md`/`activeContext.md` are **out of
+v1**.
Every generated file carries a **provenance header** ("generated by /v:onboard from cited
evidence on ; refresh with /v:onboard --refresh") and a link to the manifest, so durable
diff --git a/docs/superpowers/specs/2026-07-17-v-onboard-operations-dimension-design.md b/docs/superpowers/specs/2026-07-17-v-onboard-operations-dimension-design.md
new file mode 100644
index 00000000..858fed3d
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-17-v-onboard-operations-dimension-design.md
@@ -0,0 +1,165 @@
+# /v:onboard — Operations / Deployment coverage dimension (design)
+
+> Fixes a coverage blind spot in the `/v:onboard` pipeline: it never documents the
+> CI/CD + DevOps layer of a project. Adds an explicit **Operations / Deployment**
+> dimension that produces a cited `docs/superpowers/architecture/operations.md`.
+> Authority doc: [`skills/compound-v/onboarding.md`](../../../skills/compound-v/onboarding.md).
+> Base design of record: [`2026-06-30-v-onboard-design.md`](2026-06-30-v-onboard-design.md).
+
+## 1. Problem
+
+`/v:onboard` builds a citation-verified architecture KB but silently skips the
+operations layer. Confirmed against the code:
+
+- **PACK includes the raw material.** `scripts/compound-v-onboard.py` `_exclude_reason`
+ drops only vendored / generated / binary paths — so `docker/**`, `.github/workflows/*`,
+ Terraform, and deploy scripts all reach EXTRACT. The material is available, not excluded.
+- **DETECT never inventories it.** `onboarding.md` §1 inventories existing instruction
+ files, stack, git remote, UI presence (`detect-ui`), style configs, cross-tool signal,
+ and nested instruction files — but **not** CI/CD pipelines, Dockerfiles/compose, or
+ deploy scripts.
+- **EXTRACT has no home for it.** Claim types are `architecture | business-logic |
+ tech-context | convention`; nothing prompts deployment/infra/CI-CD coverage. The fixed
+ arch doc set is `architecture.md` / `business-logic.md` / `tech-context.md`.
+- **`.github` appears only as untrusted `copilot-instructions` and as a high-impact
+ taxonomy path** — never as a documentation dimension.
+
+**Net effect:** unless the operator hand-adds an ops step, Docker topology, GitHub Actions
+deploy, production domain, and runbooks are silently dropped from the generated KB —
+becoming confident partial truth downstream (the exact failure PACK's "silently dropped
+relevant file" caveat warns about).
+
+## 2. Scope
+
+- **In:** the `/v:onboard` pipeline only — DETECT, EXTRACT, a new conditional
+ `operations.md`, the WRITE surface, and the refresh/staleness manifest; a deterministic
+ `detect-ops` subcommand + selftest; the spec artifacts table.
+- **Out:** the brainstorm→execute pre-flights (`code-archaeologist`, `domain-expert`,
+ `doc-validator`) are a separate subsystem and are **not** touched. No infra
+ provisioning, no secret extraction into the doc (the existing `scan-output` blocking
+ gate already refuses credentials in generated files). No `verify-citations` change —
+ the claim `type` field is free-form data there.
+
+## 3. Decisions (resolved during brainstorm)
+
+1. **Gating = deterministic `detect-ops` subcommand**, mirroring `detect-ui` — not a
+ prose-only DETECT glob. Consistent with how `detect-ui` gates `DESIGN.md`.
+2. **A 5th claim type `operations`** (not a `tech-context` reuse) — operations claims
+ target `operations.md`. Cleanest `type → doc` mapping; free-form `type` means no
+ `verify-citations` change.
+3. **Onboarding pipeline only** — no new pre-flight agent.
+4. **The "include DevOps?" ask lives at the HUMAN GATE (§6)**, not at DETECT.
+ `operations.md` is generated then presented as its own explicit per-artifact confirm.
+ A fully autonomous / unattended run (auto-approve / `--permission-mode dontAsk` —
+ today the headless marathon, or any future autonomous onboarding cycle) auto-approves
+ it, exactly as the gate already handles every other artifact. No separate
+ autonomous-mode wiring is needed.
+
+## 4. Design
+
+### 4.1 `detect-ops` (deterministic, `scripts/compound-v-onboard.py`)
+
+Mirrors `detect_ui`, but ops has sub-categories, so it returns a small dict rather than a
+bare bool:
+
+```
+detect_ops(repo) -> {
+ "signals_found": bool, # true iff >=1 KNOWN signal matched. FALSE = "no signals found",
+ # NOT "no ops layer" — a bespoke ship.sh matches nothing yet exists.
+ "ci_cd": [paths...],
+ "containers": [paths...],
+ "deploy": [paths...],
+}
+```
+
+Signal set (documented in-code; matched by walking the filesystem, excluding `VENDOR_DIRS` —
+not `git ls-files`, so the non-git `--selftest` temp trees also detect. Ops files are effectively
+always tracked, so this does not diverge from the git-tracked PACK/scope-gate in practice):
+
+- **CI/CD:** `.github/workflows/*.yml|*.yaml`, `.gitlab-ci.yml`, `.circleci/config.yml`,
+ `Jenkinsfile`, `azure-pipelines.yml`, `.travis.yml`, `bitbucket-pipelines.yml`.
+- **Containers / infra:** `Dockerfile` (+ `Dockerfile.*`, nested `**/Dockerfile`),
+ `docker-compose*.yml|.yaml`, `compose.yml|.yaml`, `*.tf` / `*.tfvars`, and k8s
+ heuristics (`k8s/` dir, `kustomization.yaml`, Helm `Chart.yaml`). k8s detection is a
+ filename/dir heuristic and is documented as such — honest about its limits, like the
+ DESIGN.md linter caveats.
+- **Deploy / PaaS:** `Procfile`, `fly.toml`, `vercel.json`, `netlify.toml`, `render.yaml`,
+ `serverless.yml`, `app.yaml`, `deploy*.sh`.
+
+CLI wiring (mirrors `detect-ui`):
+
+- `add_parser("detect-ops")` with `--repo` (default `.`) and `--json`.
+- `main()`: `detect-ops` prints `ops` / `no-signals` by default (deliberately **not** `no-ops` —
+ the empty case is an open question, not an absence verdict); with `--json`, prints the grouped
+ inventory dict. Exit 0.
+- **Selftest** in the existing selftest block: `detect_ops(...)["signals_found"] is True` on a
+ fixture containing a `.github/workflows/ci.yml` (or `Dockerfile`); `... is False` **with empty
+ category lists** on a bare tree (asserting the empty result carries no false verdict) — matching
+ the shape of the existing `detect_ui` true/false selftests.
+
+### 4.2 `onboarding.md` authority-doc edits
+
+- **§1 DETECT** — add an **Operations / Deployment** bullet: run
+ `python3 scripts/compound-v-onboard.py detect-ops --repo . --json`; inventory the three
+ categories. Silent inventory, like `detect-ui` — the inclusion *ask* is at the gate, not
+ here. This is the deterministic gate for the `operations.md` branch.
+- **§3 EXTRACT** — claim `type` enum becomes
+ `architecture | business-logic | tech-context | convention | operations`. Operations
+ claims carry `target_doc_section` pointing at `operations.md`. Load-bearing rules still
+ bite: a deploy-secret path, a production/branch deploy gate, or a fail-closed CI check is
+ **load-bearing** (`security` / `fail-closed`) and blocks on unsupported per the existing
+ two-tier gate.
+- **New "operations.md" section** (parallel to the CONVENTIONS.md / DESIGN.md section) —
+ `operations.md` is generated when `detect-ops` found signals (`signals_found: true`) **or** when
+ the maintainer answers the GATE's open question by naming a bespoke deployer the signal list
+ missed. It is skipped **only** when `signals_found: false` **and** the human confirmed there is
+ genuinely nothing — never silently on an empty scan (verify BOTH the found path and the
+ open-question path on dogfoods). Read-then-cite from real workflow / Docker / deploy files (or the
+ file the maintainer pointed at). Covers: container topology, CI/CD stages, deploy target +
+ production domain, runbook pointers. Never extracts a credential — `scan-output` (§7) still refuses.
+- **§6 HUMAN GATE** — the detector is an accelerator, never a verdict, so the gate surfaces ops in
+ **both** branches:
+ - `signals_found: true` → `operations.md` as its **own explicit per-artifact confirm**, framed
+ with the detected inventory: *"DevOps/deployment tooling detected: `` — include `operations.md`?"* Decline → dropped.
+ - `signals_found: false` → **not** a silent skip but an **open question**: *"No explicit ops files
+ detected — if this project deploys, point me at it (e.g. a hand-rolled `ship.sh`)."* Human names
+ it → documented; human confirms nothing → skipped. The human, not the heuristic, decides.
+
+ Under a fully autonomous / unattended run the gate auto-approves the `signals_found: true` doc
+ (ops taken into account without asking — no new code path); with `signals_found: false` and no
+ human, it records "no signals found (not confirmed absent)" rather than asserting no ops layer.
+- **§7 WRITE surface** — add `docs/superpowers/architecture/operations.md` to the approved
+ v1 write set. It is a normal cited architecture doc: provenance header, output secret
+ gate, commit-before-index all apply unchanged.
+- **Refresh / §9 manifest** — `operations.md` is a normal cited arch doc, so it rides the
+ existing `.onboard-manifest.json` cited-evidence staleness machinery with no new gate.
+
+### 4.3 Spec `2026-06-30-v-onboard-design.md`
+
+- Add an artifacts-table row:
+ `operations.md | docs/superpowers/architecture/ | ops signals found or maintainer-pointed, confirmed | citation hybrid (§7)`.
+- Note `operations.md` as the **conditional fourth** architecture doc (the durable set is
+ three-always + `operations.md`-when-ops), consistent with the Cline Memory Bank framing.
+
+## 5. Files touched
+
+| File | Change |
+|---|---|
+| `scripts/compound-v-onboard.py` | `detect_ops()` + `detect-ops` CLI parser/output + selftest |
+| `skills/compound-v/onboarding.md` | §1 DETECT bullet, §3 EXTRACT type, new operations.md section, §6 gate confirm, §7 write surface, refresh note |
+| `docs/superpowers/specs/2026-06-30-v-onboard-design.md` | artifacts-table row + conditional-fourth prose |
+
+No `verify-citations` / claims-schema change. No pre-flight change.
+
+## 6. Verification
+
+- `detect_ops` selftest passes (`signals_found`-true on fixture; `signals_found`-false **with empty
+ lists** on bare) inside the existing `python3 scripts/compound-v-onboard.py selftest` run; whole
+ selftest stays green.
+- `detect-ops --json` returns the grouped inventory on a real ops repo (e.g. the Laravel+Vue
+ dogfood with `docker/**` + `.github/workflows/ci.yml`); `no-signals` on a bare tree.
+- Manual pipeline read-through, **both** gate branches: an ops repo surfaces the confirm and, on
+ approval, writes a cited `operations.md`; a signal-less repo surfaces the **open question** (not a
+ silent skip) — the doc is written if the maintainer points at a bespoke deployer, skipped only if
+ they confirm none.
diff --git a/scripts/compound-v-onboard.py b/scripts/compound-v-onboard.py
index 975f1c7b..47c7a5de 100644
--- a/scripts/compound-v-onboard.py
+++ b/scripts/compound-v-onboard.py
@@ -336,6 +336,83 @@ def detect_ui(repo: str) -> bool:
return False
+# Operations-file taxonomy, kept as named signal sets so the surface is documented in ONE place and
+# widening coverage is a data edit, not new control flow. Two literal kinds, matched by _ops_category:
+# _OPS_PATH_FILES — full repo-relative path (root-anchored configs like .circleci/config.yml)
+# _OPS_BASE_FILES — exact basename, at any depth (Jenkinsfile, Procfile, ...)
+# The remaining signals are shape-based (prefix/suffix/path-segment) and live in the predicates below.
+_YAML_EXT = (".yml", ".yaml")
+_OPS_PATH_FILES = {
+ "ci_cd": frozenset((".gitlab-ci.yml", ".circleci/config.yml", ".travis.yml",
+ "azure-pipelines.yml", "bitbucket-pipelines.yml")),
+}
+_OPS_BASE_FILES = {
+ "ci_cd": frozenset(("jenkinsfile",)),
+ "containers": frozenset(("kustomization.yaml", "chart.yaml")),
+ "deploy": frozenset(("procfile", "fly.toml", "vercel.json", "netlify.toml",
+ "render.yaml", "serverless.yml", "app.yaml")),
+}
+
+
+def _is_ci_cd(low, base):
+ return (base in _OPS_BASE_FILES["ci_cd"]
+ or low in _OPS_PATH_FILES["ci_cd"]
+ or (low.startswith(".github/workflows/") and low.endswith(_YAML_EXT)))
+
+
+def _is_containers(low, base):
+ return (base == "dockerfile" or base.startswith("dockerfile.")
+ or base in _OPS_BASE_FILES["containers"]
+ or low.endswith((".tf", ".tfvars"))
+ or ((base.startswith("docker-compose") or base.startswith("compose.")) and low.endswith(_YAML_EXT))
+ # k8s: filename/dir heuristic — it cannot see manifest content.
+ or low.startswith("k8s/") or "/k8s/" in low)
+
+
+def _is_deploy(low, base):
+ return (base in _OPS_BASE_FILES["deploy"]
+ or (base.startswith("deploy") and base.endswith(".sh")))
+
+
+# Evaluated in order; the first category whose predicate matches wins.
+_OPS_RULES = (("ci_cd", _is_ci_cd), ("containers", _is_containers), ("deploy", _is_deploy))
+
+
+def _ops_category(rel: str):
+ """Classify a repo-relative path into an operations category (ci_cd | containers | deploy),
+ or None. Signal surface lives in the _OPS_* sets and the _is_* predicates above."""
+ low = rel.lower()
+ base = low.rsplit("/", 1)[-1]
+ for category, matches in _OPS_RULES:
+ if matches(low, base):
+ return category
+ return None
+
+
+def detect_ops(repo: str) -> dict:
+ """Inventory CI/CD + container/infra + deploy files. Walks the filesystem (excluding VENDOR_DIRS)
+ so it works on non-git trees too. Reads only filenames (os.walk, no file contents), so the
+ hardened bounded-read path (_open_regular/_read_bounded) does not apply here.
+
+ `signals_found` is True iff at least one KNOWN signal matched. Its falsity means "no signals
+ found" — NOT a verdict that the project has no ops layer. The signal list is a fixed accelerator
+ for the common case; a bespoke deployer (e.g. `ship.sh`) matches nothing, so an empty result is
+ an OPEN QUESTION the gate must surface ("no explicit ops files — if this project deploys, point
+ me at it"), never a confident "no ops". An incomplete scan must never read as a clean one."""
+ found = {"ci_cd": [], "containers": [], "deploy": []}
+ for dirpath, dirnames, filenames in os.walk(repo):
+ dirnames[:] = [d for d in dirnames if d not in VENDOR_DIRS]
+ for fn in filenames:
+ rel = os.path.relpath(os.path.join(dirpath, fn), repo).replace(os.sep, "/")
+ cat = _ops_category(rel)
+ if cat:
+ found[cat].append(rel)
+ for k in ("ci_cd", "containers", "deploy"):
+ found[k].sort()
+ found["signals_found"] = any(found[k] for k in ("ci_cd", "containers", "deploy"))
+ return found
+
+
def _design_result_ok(result: dict) -> bool:
return int(result.get("summary", {}).get("errors", 1)) == 0
@@ -1685,6 +1762,27 @@ def check(name, cond):
shutil.rmtree(d5, ignore_errors=True)
check("detect_ui false on bare", detect_ui(tempfile.mkdtemp()) is False)
+ # detect_ops: CI/CD + container + deploy inventory (walks fs, not git — selftest dirs aren't repos).
+ d5b = tempfile.mkdtemp()
+ try:
+ os.makedirs(os.path.join(d5b, ".github", "workflows"))
+ with open(os.path.join(d5b, ".github", "workflows", "ci.yml"), "w") as fh: fh.write("on: push\n")
+ with open(os.path.join(d5b, "Dockerfile"), "w") as fh: fh.write("FROM alpine\n")
+ with open(os.path.join(d5b, "fly.toml"), "w") as fh: fh.write("app='x'\n")
+ r_ops = detect_ops(d5b)
+ check("detect_ops signals_found true on ci+docker", r_ops["signals_found"] is True)
+ check("detect_ops finds ci_cd workflow", ".github/workflows/ci.yml" in r_ops["ci_cd"])
+ check("detect_ops finds container Dockerfile", "Dockerfile" in r_ops["containers"])
+ check("detect_ops finds deploy fly.toml", "fly.toml" in r_ops["deploy"])
+ finally:
+ shutil.rmtree(d5b, ignore_errors=True)
+ # Bare tree ⇒ signals_found False. This is "no signals found" (an open question for the gate),
+ # never a "no ops layer" verdict — a bespoke deployer would match nothing yet still exist.
+ d5c = detect_ops(tempfile.mkdtemp())
+ check("detect_ops signals_found false on bare", d5c["signals_found"] is False)
+ check("detect_ops empty lists on bare (no false verdict, just no signals)",
+ d5c["ci_cd"] == [] and d5c["containers"] == [] and d5c["deploy"] == [])
+
# OUTPUT-side secret gate: blocks a secret in a GENERATED doc, passes clean prose.
d6 = tempfile.mkdtemp()
try:
@@ -2371,6 +2469,7 @@ def build_parser():
sp = sub.add_parser("design-lint")
sp.add_argument("--file", required=True); sp.add_argument("--json", action="store_true")
sp = sub.add_parser("detect-ui"); sp.add_argument("--repo", default=".")
+ sp = sub.add_parser("detect-ops"); sp.add_argument("--repo", default="."); sp.add_argument("--json", action="store_true")
sp = sub.add_parser("scan-output")
sp.add_argument("--files", nargs="+", required=True)
sp.add_argument("--repo", default="."); sp.add_argument("--json", action="store_true")
@@ -2420,6 +2519,14 @@ def main(argv) -> int:
if args.cmd == "detect-ui":
print("ui" if detect_ui(os.path.abspath(args.repo)) else "no-ui")
return 0
+ if args.cmd == "detect-ops":
+ result = detect_ops(os.path.abspath(args.repo))
+ if args.json:
+ print(json.dumps(result, indent=2))
+ else:
+ # "no-signals" (an open question for the gate), NOT "no-ops" (a false absence verdict).
+ print("ops" if result["signals_found"] else "no-signals")
+ return 0
if args.cmd == "scan-output":
result = scan_output_files(os.path.abspath(args.repo), args.files)
print(json.dumps(result, indent=2))
diff --git a/skills/compound-v/onboarding.md b/skills/compound-v/onboarding.md
index 1cb74ed8..6165dce1 100644
--- a/skills/compound-v/onboarding.md
+++ b/skills/compound-v/onboarding.md
@@ -8,7 +8,7 @@
`/v:onboard` studies an existing repository and builds a **trusted, citation-verified knowledge
base** (`docs/superpowers/architecture/*`) plus **cross-tool agent instructions** (`AGENTS.md` +
-a thin `CLAUDE.md` bridge, root `CONVENTIONS.md`, conditional `DESIGN.md`) — all behind a human
+a thin `CLAUDE.md` bridge, root `CONVENTIONS.md`, conditional `DESIGN.md`, conditional `operations.md`) — all behind a human
approval gate — then feeds them into V-memory. What onboarding writes becomes recall
(`/v:remember`) and pre-flight context for the orchestrator. It **extends** `docs/superpowers/**`;
it never rewrites the recall engine or the routing layer.
@@ -55,6 +55,21 @@ Inventory the ground truth, write nothing:
- **Existing instruction files** (treat per the cardinal rule above), stack, git remote origin.
- **UI presence** via `python3 scripts/compound-v-onboard.py detect-ui --repo .` → `ui` / `no-ui`.
This is the only thing that decides whether the DESIGN.md branch runs (step 9 / §DESIGN below).
+- **Operations / Deployment signals** via `python3 scripts/compound-v-onboard.py detect-ops
+ --repo . --json` → `{signals_found, ci_cd[], containers[], deploy[]}`. Inventories CI/CD
+ (`.github/workflows/*`, `.gitlab-ci.yml`, `.circleci/config.yml`, `Jenkinsfile`,
+ `azure-pipelines.yml`, `.travis.yml`, `bitbucket-pipelines.yml`), container/infra
+ (`Dockerfile*`, `docker-compose*`/`compose.*`, `*.tf`/`*.tfvars`, and k8s heuristics —
+ `k8s/`, `kustomization.yaml`, Helm `Chart.yaml`), and deploy/PaaS (`Procfile`, `fly.toml`,
+ `vercel.json`, `netlify.toml`, `render.yaml`, `serverless.yml`, `app.yaml`, `deploy*.sh`).
+ Silent inventory like `detect-ui` — the *include-it?* ask lives at the GATE (§6), not here.
+ The fixed signal list is a **common-case accelerator, not a verdict**: `signals_found: true`
+ proposes the operations.md branch with the found files; `signals_found: false` means **"no
+ signals found," NOT "this project has no ops layer"** — a bespoke deployer (e.g. a hand-rolled
+ `ship.sh`) matches nothing yet still exists. So the empty case is **never a silent skip**; it
+ becomes an **open question at the GATE** (§6). An incomplete scan must never read as a clean
+ one. k8s detection is a filename/dir heuristic (it cannot see manifest content) — stated as
+ such here and in the operations.md doc section.
- **Style configs**: eslint / prettier / ruff / editorconfig / tsconfig / lockfiles — the
deterministic evidence `CONVENTIONS.md` is later derived from.
- **Cross-tool signal** for the bridge decision: presence of `.cursor*`, `.windsurf*`, `GEMINI.md`,
@@ -82,13 +97,21 @@ load-bearing.
Generation is **read-then-cite**: open the files, claim only what you actually read, and attach a
`file:line` citation to every architecture / business-logic claim. Emit a **claims file** in the
schema VERIFY consumes (locked in "Shared Interfaces"): each claim carries `text`, `type`
-(`architecture | business-logic | tech-context | convention`), `citations[{path,startLine,endLine}]`,
+(`architecture | business-logic | tech-context | convention | operations`), `citations[{path,startLine,endLine}]`,
`load_bearing` + `load_bearing_reason` (`security | fail-closed | concurrency | other`), `confidence`,
and `target_doc_section`.
A claim is **load-bearing** when it concerns **security, fail-closed behavior, or concurrency** —
the claims where being confidently wrong is dangerous.
+`operations` claims (CI/CD, container topology, deploy target, runbook pointers) target
+`operations.md` and are emitted when DETECT found ops signals (`signals_found: true`) **or** when
+the maintainer answered the GATE's open question by pointing at a bespoke deployer the signal list
+missed (§6). The load-bearing rule still bites: a deploy-secret path, a production/branch deploy
+gate, or a fail-closed CI check is **load-bearing** (`security` / `fail-closed`) and blocks on
+unsupported per the two-tier gate (§4) like any other load-bearing claim. `type` is free-form to
+`verify-citations`, so this adds no schema change.
+
### 4. VERIFY — the two-tier citation gate
Hand the claims file to `python3 scripts/compound-v-onboard.py verify-citations --claims FILE
[--tier2 FILE] --repo . --json`.
@@ -148,6 +171,25 @@ Also flag drift from `python3 scripts/compound-v-onboard.py staleness --repo .`
Present, for approval, a **per-artifact AND per-section diff**, alongside confidence/staleness and
the diagnosis. **Nothing is written before explicit approval** — no auto-apply, ever.
+**Operations coverage — surface it in BOTH branches; the detector is an accelerator, never a verdict:**
+
+- **`signals_found: true`** — present `operations.md` as its **own explicit per-artifact confirm**,
+ framed with the detected inventory: *"DevOps/deployment tooling detected —
+ `` — include `operations.md`?"* Declining drops the doc
+ and writes nothing for it; this is the *"ask the user whether to take DevOps into account"* decision.
+- **`signals_found: false`** — do **not** conclude "no ops layer" and skip silently. The fixed signal
+ list is blind to bespoke deployers, so surface the gap as an **open question**, not a verdict:
+ *"No explicit ops files detected. If this project does deploy, point me at it (e.g. a hand-rolled
+ `ship.sh`, a Makefile target, an internal runbook) and I'll document it in `operations.md`."* If the
+ maintainer names something, EXTRACT reads-then-cites it into `operations.md`; if they confirm there
+ is genuinely nothing, it is skipped — but the **human**, not the heuristic, made that call.
+
+A fully autonomous / unattended run (auto-approve / `--permission-mode dontAsk`) auto-approves the
+`signals_found: true` doc like every other artifact (ops taken into account **without asking** — no
+separate code path). With `signals_found: false` and no human to answer, it records **"ops coverage:
+no signals found (not confirmed absent)"** rather than asserting there is no ops layer — the open
+question survives to the next interactive pass instead of being silently resolved as "none".
+
Critically, the diff **expands every `@import` target** (to the 4-hop limit). `@import` is **not a
token optimization** — an imported file loads in **full** at launch; only path-scoped rules and
skills defer. So an approver must see *what actually loads after this change*, not just the literal
@@ -179,7 +221,8 @@ section before proceeding. **This** is the gate that enforces "no credential rea
committed file" — not the advisory input pack scan (§2), which would over-block on benign fixtures.
Write **only** what was approved, and **only** within the v1 write surface:
-`docs/superpowers/architecture/*`, root `CONVENTIONS.md`, root `DESIGN.md` (UI repos), `AGENTS.md`,
+`docs/superpowers/architecture/*` (including `operations.md` — **only when the ops gate was approved**,
+§6), root `CONVENTIONS.md`, root `DESIGN.md` (UI repos), `AGENTS.md`,
the thin `CLAUDE.md` bridge, `.onboard-manifest.json`, `.claude/rules/*.md` (§Path-scoped rules), and —
**only when the user confirms the diff** — `.mcp.json` (from `mcp_json_config`: merged **additively**,
never clobbering an existing server; CLI recommendations like `gh` are surfaced as setup instructions,
@@ -222,7 +265,10 @@ the Pre-Evaluation stage see them; they are static-evidence inputs, not recall p
manifest — they carry no FTS5 obligation. The `--docmap` you pass **includes every**
**`.claude/rules/*.md`** with the files its rules cite: `write_manifest` replaces the manifest
wholesale, so a rule omitted from the docmap is silently de-registered and stops being
-staleness-tracked. See §Path-scoped rules.
+staleness-tracked. See §Path-scoped rules. **An approved `operations.md` is a docmap key too**
+(`{"docs": {"docs/superpowers/architecture/operations.md": [], …}}`) — omit it and
+`staleness` never flags a stale `operations.md`; a bare `--write` with no `--docmap` writes an EMPTY
+manifest, de-registering everything.
---
@@ -269,6 +315,22 @@ explore → ask → propose → write.
green). Therefore the gate states **"token pairs pass WCAG AA structurally"** — **never
"accessible."** Document the linter's blindness in the gate output, and flag multi-theme / arbitrary
Tailwind class colors as "partial capture" rather than implying full coverage.
+- **`operations.md`** (`docs/superpowers/architecture/`, ops repos) is generated when `detect-ops`
+ found signals (`signals_found: true`) **or** when the maintainer answered the GATE's open question
+ (§6) by naming a bespoke deployer the signal list missed. It is skipped **only** when
+ `signals_found: false` **and** the human confirmed there is genuinely nothing — never silently on an
+ empty scan alone (the "no signals found ≠ no ops layer" distinction is the whole point; verify both
+ the found path and the open-question path on dogfoods). Read-then-cite from the real workflow /
+ Docker / compose / Terraform / deploy files DETECT inventoried (or the file the maintainer pointed
+ at) — never from the model's prior. Cover: container topology (services, ports, volumes), CI/CD
+ stages (build → test → deploy triggers and branch/environment gates), the deploy target +
+ production domain, and runbook pointers. Citations obey the same Tier-1 containment gate (§4) —
+ a cited path that is absolute, walks `..`, escapes the repo via symlink, or names a non-regular
+ file is `path-escapes-repo`/`not-a-regular-file` and blocks. **No credential is ever extracted
+ into the doc** — the blocking `scan-output` gate (§7) refuses a generated file that contains one,
+ and a deploy-secret reference is documented by *path*, not value. It is a normal cited arch doc, so
+ it rides the same `.onboard-manifest.json` cited-evidence staleness machinery (§9 / §Refresh) with
+ no new gate.
---