From c66b21c12eb2e0b3df1ea163b4840870607bd260 Mon Sep 17 00:00:00 2001 From: khymerao Date: Sat, 5 Sep 2026 09:25:17 +0200 Subject: [PATCH] feat(v-onboard): Operations/Deployment dimension, rebased onto v3.5.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebase of #4 onto v3.5.1 (branch had drifted two /v:onboard rewrites: 3.4.x, 3.5.0). Re-placed against current anchors; addresses review. - scripts/compound-v-onboard.py: detect-ops next to detect-ui in build_parser() + main() dispatch; detect_ops/_ops_category/_OPS_* + 6 selftest rows (165 ok). detect_ops reads only filenames (os.walk), never file contents, so the hardened _open_regular/_read_bounded path does not apply — noted in the docstring. - Reviewer's core ask (accelerator, not verdict): signals_found (not 'present'); CLI prints 'no-signals' not 'no-ops'; GATE surfaces BOTH branches — found -> confirm, no-signals -> open question ('point me at your deployer, e.g. ship.sh'). An empty scan is never a silent skip. - onboarding.md: re-placed DETECT / EXTRACT (5th claim type 'operations') / GATE / WRITE surface / operations.md doc section / INDEX docmap / intro against the 3.5.0 restructure. operations.md registered as a docmap key for .onboard-manifest.json ({"docs": {operations.md: [cited]}}); citations obey the new Tier-1 containment gate. - specs: base-spec table row + conditional-fourth prose; CHANGELOG Unreleased entry; commands/v-onboard.md non-negotiable #7. Gates green locally: --selftest (165 ok), lint-frontmatter, rules-lint, dead-link. --- CHANGELOG.md | 8 + commands/v-onboard.md | 2 + ...26-07-17-v-onboard-operations-dimension.md | 359 ++++++++++++++++++ .../specs/2026-06-30-v-onboard-design.md | 10 +- ...7-v-onboard-operations-dimension-design.md | 165 ++++++++ scripts/compound-v-onboard.py | 107 ++++++ skills/compound-v/onboarding.md | 70 +++- 7 files changed, 714 insertions(+), 7 deletions(-) create mode 100644 docs/superpowers/plans/2026-07-17-v-onboard-operations-dimension.md create mode 100644 docs/superpowers/specs/2026-07-17-v-onboard-operations-dimension-design.md 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. ---