ss-bs-discovering-architecture — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ss-bs-discovering-architecture (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are loaded inline by ss-bs-bootstrapping-project (NOT dispatched as a subagent). Architecture facts come from two places — the code (layout, Dockerfiles, k8s manifests, dependency files, env vars) and the user's head (what's deliberately OUT of scope, why a relationship is N:N rather than 1:N, which integrations are critical-path vs nice-to-have, deployment topology not visible in repo). A subagent could extract the first half from one read pass but couldn't have the back-and-forth needed for the second. So this skill stays in the coordinator's context.
Core principle: Architecture is observed, not aspirational. Describe what the code actually is, not what someone wishes it was. If there are clear smells (a service half-extracted, a monolith hiding behind microservice naming), surface them to the user — don't paper over them, don't editorialize either.
Announce at start: "I'm using the ss-bs-discovering-architecture skill to build docs/ARCHITECTURE.md with you."
You're invoked when the user picked Create / Extend / Replace for architecture. The coordinator passes you:
REPO_ROOT — absolute path to the repo rootMODE — create, extend, replace, or audit. Audit invokes the drift-check path (Step 1.6) and the drift-resolution Q0 in Step 3.EXISTING_CONTENT — verbatim current architecture-file content (only for extend / replace; empty otherwise)FILE_PATH — target write path (typically docs/ARCHITECTURE.md; honors context.architecture_path config override if non-default)on or off. When on, run Step 1.5 (silent diagnose) and surface Q1.5 in Step 3. When off, skip both — identical to pre-suggestion-pass behaviour. Defaulted by the coordinator from suggest.default in config and the opt-in question at bootstrap start. Always on in audit mode.extend mode. Extend merges; only replace overwrites.on (regardless of input). This is documented in the Inputs section.┌─────────────────────────────────────────────────────┐
│ MODE = create or replace │
│ → Step 1: silent code scan (layout, build, …) │
│ → Step 1.5: silent diagnose (if SUGGEST=on) │
│ → Step 2: announce findings (+ diagnoses) │
│ → Step 3: targeted questions (Q1, Q1.5 if SUGGEST,│
│ then Q2-Q5) │
│ → Step 4: synthesize draft → show to user │
│ → Step 5: refine via tweak loop (cap 3) │
│ → Step 6: atomic write │
├─────────────────────────────────────────────────────┤
│ MODE = extend │
│ → Step 1: silent code scan + read EXISTING_CONTENT│
│ → Step 1.5: silent diagnose (if SUGGEST=on) │
│ → Step 2: announce findings + gaps + diagnoses │
│ → Step 3: targeted questions on gaps + Q1.5 │
│ → Step 4: synthesize additions → show diff │
│ → Step 5: refine via tweak loop (cap 3) │
│ → Step 6: atomic write of merged content │
├─────────────────────────────────────────────────────┤
│ MODE = audit │
│ → Step 1: silent code scan │
│ → Step 1.5: silent diagnose (always on for audit) │
│ → Step 1.6: drift check vs EXISTING_CONTENT │
│ → Step 2: announce findings + drift + diagnoses │
│ → Step 3: Q0 (drift resolution) → Q1 → Q1.5 → ...│
│ → Step 4-6: as usual │
└─────────────────────────────────────────────────────┘Read all of the following that exist. Don't narrate progress to the user — this happens silently, then you announce findings once in Step 2.
Run a tree-like listing of the repo's top 2-3 levels. Note:
src/, lib/, app/, language-specific layouts)services/, apps/, packages/)infra/, terraform/, k8s/, docker/, deploy/)docs/, documentation/)tests/, test/, __tests__/, spec/)node_modules/, vendor/, target/, build/, dist/ — usually ignore)If you see multiple service-like subdirs (e.g., services/billing/, services/checkout/), this is a multi-service repo even if not formally a monorepo. Note that.
Read the relevant ones:
package.json (root + per-workspace if monorepo), pnpm-workspace.yaml, lerna.json, nx.json, turbo.jsonpyproject.toml, setup.py, requirements*.txt, Pipfile, poetry.lockCargo.toml (root + per-workspace), Cargo.lockgo.mod, go.sumpom.xml, build.gradle*, settings.gradle*Gemfile, gemspec*.csproj, *.slnMakefile, justfile, Taskfile.ymlExtract: language(s), framework(s), key libraries (web framework, ORM, queue client, HTTP client, etc.), build tooling, package manager.
Identify them by looking for:
main, index, app, server, cli files at common paths (src/main.*, src/index.*, cmd/<name>/main.go, app/main.py, etc.)package.json's bin field or scripts.start / scripts.devworker.*, job.*, cron.*, or queue-consumer code patterns)argparse/clap/commander/click imports)For each entry point, one-line description: what process does it run? When is it invoked?
Read:
Dockerfile and Dockerfile.* variants — what runtime is the prod artifact?docker-compose.yml and docker-compose.*.yml — local dev topologyk8s/*.yaml, manifests/*.yaml — production topologyterraform/*.tf, pulumi/*.py, cdk/*.ts — infrastructure-as-codeProcfile — Heroku-style servicesfly.toml, render.yaml, vercel.json, netlify.toml — PaaS configsserverless.yml, sam.yaml — serverlessExtract: what processes run? Where? How do they communicate (HTTP, queues, RPC)? Is there a load balancer / API gateway / reverse proxy in front?
Look for:
pg, mysql2, psycopg, sqlx, gorm, prisma, mongoose, mongodb, redis, etc.DATABASE_URL, REDIS_URL, KAFKA_BROKERS, etc.migrations/, db/migrations/, prisma/migrations/docker-compose.yml services like postgres, redis, clickhouse, kafka, rabbitmqFor each store, one-line purpose: what does this codebase use it for? (If not obvious from config alone, note it and ask the user in Step 3.)
The fastest signal: .env.example / .env.sample. List the third-party services hinted at by env-var names:
STRIPE_* → Stripe (payments)SENDGRID_* / MAILGUN_* / RESEND_* → emailOPENAI_* / ANTHROPIC_* → LLM APIsAWS_* → AWS (S3, SQS, etc.)SENTRY_* / DATADOG_* → observabilityAUTH0_* / CLERK_* / OKTA_* → identityTWILIO_* → SMS/voiceCross-reference with SDK imports in source (import Stripe from 'stripe', etc.) to confirm. Env-var-only signals get flagged for the user in Step 3 — don't list them as confirmed integrations.
What's in scope for this codebase vs out:
vendor/ or third_party/ → out of scope (don't describe it)EXISTING_CONTENT (it's empty). Build candidate sections from scratch.EXISTING_CONTENT and identify which of the six sections are missing, outdated, or incomplete. Candidate additions focus on gaps.EXISTING_CONTENT. Build candidates fresh.Hold internally:
SUGGEST=on)If SUGGEST=off, skip this step entirely and proceed to Step 2.
Diagnose looks for anti-patterns and missing-but-typically-valuable structural decisions in the architecture. Every diagnose finding must be evidence-cited with specific file paths or concrete counts. Abstract "you should do X" findings are not allowed.
For each category below, scan additional files if needed (within the budget — see Hard Gates). Scan each category for candidates. One strong candidate per category is the target; the aggregate cap of 5 is enforced in 1.5b after dropping unsupported candidates.
common/, shared/, or lib/ directory imported by all services with no documented ownership rules. Evidence: directory path + import counts.app.listen/equivalent.Each candidate must include:
severity: one of MUST, SHOULD, INFO — see Hard Gates for the matching evidence bartitle: one-line headline (e.g., "Declare cross-service DB access policy")evidence: specific file paths or counts (e.g., services/billing/src/invoice.ts:34, .../report.ts:88, .../sync.ts:12)proposed_addition: exact markdown text to add to the artifact (a new section, a new component entry, etc.)Drop any candidate that cannot be cited with specific evidence. Drop any candidate where the severity guess cannot be justified from the evidence (no MUST without observable harm).
If more than 5 candidates remain after dropping unsupported ones, rank by:
Surface the top 5. If 0 candidates remain, the candidate list is empty and Q1.5 in Step 3 is skipped silently.
MODE=audit)Compare each entry in EXISTING_CONTENT against current code state. The goal is to detect entries that have gone stale.
For each architecture-doc entry, check:
k8s/ shows traefik ingress instead. Evidence: file paths.docker-compose.yml now also lists ClickHouse. Evidence: compose file path + line..env.example adds SLACK_WEBHOOK_URL (new integration) or no longer mentions SENDGRID_* (removed). Evidence: file path + grep diff.Each finding: kind (component-added / component-removed / topology-change / store-added / store-removed / integration-added / integration-removed / boundary-stale / aspiration-met / aspiration-still-pending), entry (the doc text being challenged), evidence (file paths showing the current code state).
No cap on drift findings — every observable drift is surfaced. The user resolves each in Q0.
One short message (3-6 sentences; 3-7 when SUGGEST=on extends with the diagnose-mention sentence). State what you scanned and the headline finding. Example:
"Here's what I picked up from the codebase: a pnpm monorepo with three services (services/billing,services/checkout,services/catalog), all TypeScript on Express + Prisma, sharingpackages/common. Runtime: docker-compose locally, k8s in prod with nginx ingress. Data stores: Postgres (per-service schemas) + Redis (queues + sessions). Confirmed integrations: Stripe, SendGrid, Sentry. A few things I want to confirm with you — component grouping, what's out of scope, and a couple of env-var-only signals. I'll show you a draft after."
If create mode and the scan found very little structure:
"I didn't find much structural signal — no Dockerfile, no k8s manifests, single src/ directory. Looks like a small library or single-file tool. I can still build an architecture overview, but it'll be brief. Want to continue?"If extend mode:
"Your existing architecture doc covers [sections]. I scanned the codebase and found gaps in [areas]. I'll ask about those, then propose additions."
If SUGGEST=on AND Step 1.5 produced ≥1 candidate, extend the announcement with: "…and I noticed a few things worth considering that aren't currently codified — I'll show those after we confirm the observed candidates."
If MODE=audit: "Audit mode. Scan + diagnose + drift check complete. Found N drift items (X component changes, Y topology changes, Z provenance re-evaluations) — I'll surface those first in the questions, then walk through observed candidates and suggestions as usual."
Ask in this order, one question per turn. Skip a question if the scan and (for extend mode) the existing file already answered it.
MODE=audit AND Step 1.6 produced ≥1 drift finding)Ask one question per drift finding (do NOT bundle — drift items often have nuanced individual resolutions):
Question: "Drift detected: <entry summary>. Current code state: <evidence>. What's the right resolution?"
Options:
- "Update the doc to match code"
- "Keep the doc — code is wrong / will be fixed"
- "Remove the entry — no longer applies"
- "Both — clarify scope (split into multiple entries)"For provenance re-evaluation findings, the question is different:
Question: "Aspirational entry '<title>' was added on <date>. Evidence at that time: <original evidence>. Current code state: <current evidence>. Has this aspiration been realized?"
Options:
- "Yes — code has caught up. Remove the provenance marker (promote to normal)."
- "No — code still has the original problem. Keep as aspirational; refresh the marker date."
- "Drop the entry — we've decided not to pursue this aspiration."Record each resolution. Apply during Step 4 (Draft Synthesis).
Only ask if the scan signal is ambiguous (e.g., multiple service-like dirs that could be one logical component, or a monorepo where the grouping affects the doc).
Question: "I see these components: [list]. How should they group in the architecture overview?"
Options (adapt to scan):
- "Monolith — one component, single deployable"
- "Layered — frontend / backend / infra"
- "Service-oriented — each service its own component"
- "Custom (I'll describe in free-form)"
Recommend the option matching the scan signal.SUGGEST=on AND Step 1.5 produced ≥1 candidate)Question: "Here are some things I'd suggest adding even though they're
not currently codified. These are opinionated — pick any you want to
include:"
Options (multi-select, one option per Step 1.5 candidate, plus a "None" escape):
- [suggestion · <severity> · <evidence-summary>] <title>
Evidence: <evidence>
Proposed addition: <one-line summary of proposed_addition>
- ... (one option per remaining candidate, up to 5)
- "None of these — keep the doc descriptive only"
Use the harness's multi-select question tool. Do not present as plain text.If the user picks none, treat as "no suggestions accepted" and proceed to Q2. Accepted suggestions are carried into Step 4 (Draft & Show) and rendered with provenance markers.
Question: "What's explicitly OUT of scope for this codebase? Examples:
- 'Mobile apps live in a separate repo (acme/mobile)'
- 'We don't host email — third-party (Postmark) handles it'
- 'No data warehousing — read-only event firehose to BigQuery downstream'
- 'Catalog data comes from an upstream service we don't own'
Free-form text. Skip if everything visible in this repo is in scope (I'll write 'nothing material is out of scope' explicitly so it's clear)."For each integration where you saw the env var but not the SDK import (e.g., STRIPE_SECRET_KEY in .env.example but no stripe package in any service):
Question: "I see `<ENV_VAR>` in `.env.example` but no matching SDK import in the source. Is this integration:"
Options:
- "Active — confirm it's used (I'll add it to the doc)"
- "Planned but not implemented yet (drop from the doc)"
- "Used via a different SDK or HTTP client (I'll add it with a note)"
- "Legacy — should be removed from .env.example"Cap this question loop at 5 uncertain integrations; if more, ask the user to scan the list themselves.
Question: "Anything about the architecture that's true but not visible from the code? Examples:
- 'Deployed to three regions (us-east, eu-west, ap-south)'
- 'Critical path depends on Stripe — outage = full degradation'
- 'Database is shared with a separate analytics team'
- 'We run blue-green deployments via the ingress controller'
Free-form text. Skip if there's nothing material to add."Only ask if the scan revealed components with ambiguous relationships (e.g., two services both connecting to the same Redis but unclear whether they share data).
Question: "Relationship between [Component A] and [Component B] — what's the cardinality?"
Options:
- "1:1 — direct one-to-one (e.g., each instance of A pairs with one B)"
- "1:N — A has many B"
- "N:N — many-to-many (often via a queue or shared store)"
- "Not a direct relationship — they touch the same infra but don't communicate"Synthesize the draft using:
Use the canonical template (see Output Template section). Show the full draft, then ask:
Question: "How does this look?"
Options:
- "Approve — write it as-is" (Recommended)
- "Tweak — I'll tell you what to change"
- "Start over — wrong direction"
- "Abort — skip this file"On Tweak: capture user's free-form notes; apply; re-show; re-ask Step 4. Cap at 3 iterations:
"We've done three rounds and the draft still isn't matching what you want. Want to: (a) keep the current draft anyway, (b) skip architecture for now, or (c) supply the file yourself — you write the markdown, I'll save it?"
On Start over: restart Step 3 from Q1 (scan findings carry over; user answers reset).
On Abort: report skipped (declined mid-skill) to the coordinator and exit.
cat > "$FILE_PATH.tmp" <<EOF
<draft content>
EOF
mv "$FILE_PATH.tmp" "$FILE_PATH"For extend mode: merge EXISTING_CONTENT + the new sections / refinements into a single document, then write atomically. Preserve existing accurate sections; replace or add only what changed.
Report to the coordinator one of:
created (mode = create, full draft written)extended (mode = extend, merged content written)replaced (mode = replace, full draft written)skipped (declined mid-skill) (user aborted partway)Each accepted Q1.5 suggestion becomes a regular section in the artifact (e.g., a new "Boundaries" subsection, a new "Components" entry, or a new top-level recommendation block). Append a provenance line at the end of the section using this exact format:
> _Added via bootstrap suggestion pass (YYYY-MM-DD)._
> _Evidence: <evidence summary from the Q1.5 candidate>._
> _Not currently enforced — declared here as an aspirational architectural rule._Replace YYYY-MM-DD with today's date. The audit skill reads this marker on re-runs to ask whether the aspiration has been realized in code.
Canonical six-section structure (omit sections only when truly nothing material applies — e.g., "External integrations" section can be removed for a self-contained library):
# Architecture Overview
## System summary
<One paragraph. What is this system? Who does it serve? Where does it run?
Keep it accessible — someone new to the project should leave this paragraph
with the right mental model.>
## Components
<List of major components/modules with one-line responsibility each.>
- **<Name>** — <responsibility>
- **<Name>** — <responsibility>
## Runtime topology
<What processes run? Where (container, serverless, host)? How do they
communicate? Include the local dev topology if it differs meaningfully
from production.>
## Data stores
<Each store with one-line purpose. Note the access pattern if relevant
(read-heavy, write-heavy, ephemeral, etc.).>
- **<Store>** — <purpose>
## External integrations
<Each third-party service with one-line purpose. Note the criticality
(hard dependency vs nice-to-have).>
- **<Service>** — <purpose>
## Boundaries
### In scope
<What this codebase owns.>
### Out of scope
<What lives elsewhere — other repos, upstream services, vendored code,
generated code.>Drafting guidelines:
| Mistake | Fix |
|---|---|
| Adding a Mermaid/PlantUML/C4 diagram | Text only — no diagram syntax of any kind |
| Inventing topology not visible in code or stated by the user | If it's not in Dockerfile/k8s/etc., ask the user before claiming it exists |
| Restating package.json contents | Architecture overview ≠ dependency list |
| Lengthy component list (15+ entries) | Group; the goal is "main moving parts" not exhaustive enumeration |
| Code snippets in the architecture file | Almost never appropriate; if used, ≤3 lines |
| Vague "uses cloud services for storage" | Name the service: "S3 for object storage" |
| Aspirational architecture ("we're moving to ...") | Document the current state; transitions belong in ADRs |
| Skipping the Boundaries section because "everything is in scope" | If everything is in scope, say so explicitly with examples of what would be out |
| Bundling multiple questions in one ask | One question per turn |
| Looping past 3 tweak iterations | Surface to user with bail options |
| Overwriting in extend mode | Extend merges; only replace overwrites |
| Surfacing a diagnose candidate without file-path evidence | Drop it; only evidence-cited candidates pass the gate |
| Surfacing >5 diagnose candidates | Hard cap is 5; rank by severity → evidence → impact, drop the rest |
| Forgetting the provenance marker on an accepted Q1.5 suggestion | Audit relies on the marker to recognize aspirational entries; without it, drift detection breaks |
| Running Step 1.5 when SUGGEST=off | Skip Step 1.5 entirely when off; do not run-but-suppress |
| Skipping Step 1.6 in audit mode | Drift detection is the audit's reason for existing |
| Bundling Q0 drift resolutions into one multi-select | Each item gets its own question — resolutions are not collectively decidable |
| Forgetting to refresh the provenance marker date when "Keep aspirational" is chosen | Audit needs the refresh to track time-since-last-evaluation |
| Forgetting to strip the provenance marker when "Promote to normal" is chosen | The marker is the audit's hook; promoting means removing it |
Architecture has a code half (layout, Dockerfiles, k8s, dependencies, env vars) and a user half (what's deliberately out of scope, which integrations are critical-path, deployment topology not visible in repo, cardinality of relationships that touch shared infra). A subagent could extract the first half from one read pass but couldn't have the back-and-forth needed for the second. Routing the conversation through the coordinator (subagent returns findings → coordinator paraphrases → user replies → coordinator re-dispatches) wastes turns and risks every paraphrase drifting from intent.
So this skill stays inline. It scans the code itself, then asks the user about the things only they know. The four sibling skills (ss-bs-discovering-constitution, ss-bs-discovering-glossary, ss-bs-discovering-domain-model, ss-bs-discovering-design) follow the same pattern for the same reason.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.