From zensu
Scan an existing repository to discover undocumented features, then review and import them into Zensu as tracked features with linked tests, docs, and source files.
How this skill is triggered — by the user, by Claude, or both
Slash command
/zensu:ghost-scanThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Scan an existing repository to discover undocumented features, then review and import them into Zensu as tracked features with linked tests, docs, and source files.
Scan an existing repository to discover undocumented features, then review and import them into Zensu as tracked features with linked tests, docs, and source files.
This is the brownfield entry point — an existing codebase whose features are not yet tracked.
Greenfield instead? No code yet, just a plan/vision doc → use /zensu:bootstrap.
Hybrid (existing code and a forward-looking plan doc)? Run this scan first to import what is built, then create the plan's not-yet-built items as planned features. No separate skill; see Phase 6.
curl -fsSL https://zensu.dev/install.sh | sh) and on PATHzensu auth login (check with zensu auth status)/zensu:bootstrap if needed)Every command accepts --json for machine-readable output; run zensu <noun> <verb> --help for the full flag set.
Execute these phases in order. Present results to the user after each phase and wait for confirmation before proceeding.
Workflow gate (first + last action). As the VERY FIRST action, run bash "$(cat "$HOME/.zensu/plugin-root")/hooks/lib/zensu-log.sh" --workflow-begin --tools "ghost_scan,ghost_apply,ghost_batch_review,create_feature,add_subfeature,create_user_journey,create_journey_step,split_feature,link_test,generate_claude_md". This marks the Zensu product workflow active so the CLI write-gate (hooks.mcpGate, default-on) recognizes this skill's zensu ghost apply / zensu features create commands as workflow-driven rather than freelance and does not block them. As the VERY LAST action (after the final phase, or on early exit), run bash "$(cat "$HOME/.zensu/plugin-root")/hooks/lib/zensu-log.sh" --workflow-end.
zensu products listzensu features list --product <product-id> --compact to load existing feature slugszensu journeys list --product <product-id> to load existing journeys for dedup: "These journeys already exist and will not be re-suggested." This feeds the Phase 2b journey-analyst lens and Phase 5.zensu ghost candidates <scan-id> to check an open scan. If a scan in "review" status exists, ask the user whether to resume or start a new scanvendor/, node_modules/, dist/, .git/, __pycache__/, .next/, build/, target/*_test.go, *.test.ts, *.test.tsx, test_*.py, *_spec.rb, *.spec.ts*.go, *.ts, *.tsx, *.py, *.java, *.rs (excluding test files)README*, docs/**/*.md, *.rst, CHANGELOG*detectedSourceFiles, detectedTestFiles, detectedDocFiles. These arrays are the only data the scan apply uses to link artifacts — it links exactly what you pass, so an empty array links zero. Never leave detectedTestFiles empty by omission: an empty array must mean "globbed and found none," not "skipped." Note: detectedDocFiles links existing doc files in the repo (READMEs, docs/*.md) — it does not generate new documentation. Authoring new wiki docs for discovered features is a separate, code-grounded task: see docs/documentation-guide.md (read the source, never dump feature metadata).detectedSourceFiles, glob the test-file patterns from step 3 within those same directories and their sibling test dirs (test/, tests/, __tests__/, spec/, specs/), and assign every match to that candidate's detectedTestFiles. A capability feature spanning multiple modules collects tests from all of its source dirs.README*, docs/**/*.md, *.rst, CHANGELOG*) in the candidate's source dirs and the repo root, and assign every match to detectedDocFiles. Linking existing docs is first-class scan data, not a bonus — an empty detectedDocFiles must mean "globbed and found none," not "skipped."Feature-level, not function-level:
authentication instead of auth-login, auth-register, auth-logoutzensu features splitTest-file completeness (treat like the source-file rule, not a bonus):
detectedTestFiles with the same rigor as detectedSourceFiles.detectedTestFiles is acceptable ONLY after globbing the candidate's source dirs confirms genuinely zero tests — never as a default for "didn't look."app.controller.spec.ts, helpers/*.spec.ts) may stay unlinked or attach to a shell/account feature — do not force them onto an unrelated candidate.Doc-file completeness (treat like the test-file rule, not a bonus):
detectedDocFiles with the same rigor as detectedTestFiles — the scan apply links exactly what you pass, so an omitted array links zero docs.detectedDocFiles is acceptable ONLY after globbing the candidate's source dirs + repo root confirms genuinely zero docs — never as a default for "didn't look."detectedDocFiles links existing docs only (READMEs, docs/*.md). It never generates new documentation — authoring docs for a feature with zero docs is a separate task flagged in Phase 6 (see docs/documentation-guide.md).Never create candidates for:
.github/, .gitlab-ci.yml, Jenkinsfile)Makefile, Dockerfile, docker-compose.yml).eslintrc, .prettierrc, golangci.yml).vscode/, .idea/)go.sum, package-lock.json, yarn.lock)vendor/, node_modules/)Confidence scores are heuristic estimates, not ML predictions. Use them to prioritize review, not as ground truth.
| Tier | Range | Criteria |
|---|---|---|
| High | >= 0.7 | Dedicated tests + clear module boundary (own package) |
| Medium | 0.4-0.7 | Either tests OR docs present, or clear boundary without tests |
| Low | < 0.4 | No tests, no docs, unclear boundary |
Building blocks:
Classify each candidate by what data the feature itself reads or writes — not by its
directory name or the product domain. Path is a weak hint; data sensitivity is the rule.
Never default to internal for un-triaged code. Present each suggestion to the user for
verification.
restricted: handles credentials, keys, secrets, auth config, health data, or
regulatory-controlled data (key management, auth/session, security tooling, compliance exports).confidential: directly reads or writes PII, customer business data, financial records, or
personal data (user profiles, payments, roadmaps, journeys, product/feature data, org
membership). This is the bulk of a typical product — when a feature touches customer data
and you are unsure between internal and confidential, choose confidential.internal: requires standard auth but handles no PII or secrets — pure infra/ops surfaces
(health checks, app shell, log viewers, aggregate-only dashboards). A feature that only displays
aggregated data is internal even if a sibling that writes the underlying records is confidential.public: exposes no sensitive data and needs no auth (landing pages, public docs, blog,
status pages).If a candidate is genuinely un-triageable at scan time, leave securityClassification unset —
the scan apply fails safe to confidential (review-gated), never internal.
The single-pass walk in Phase 2 is only a seed. A lone heuristic pass misses
features that span modules, live behind entry points, or are only legible from
tests, data models, or docs. Augment it — never replace it — with a parallel
fan-out of read-only analysis lenses, then consolidate in this main thread. This
is the house pattern already used by /zensu:plan-review and /zensu:tdd Phase 6.
Spawn the lenses in ONE parallel batch. Use the Agent tool with
subagent_type: Explore (read-only — the lenses cannot mutate the repo or the
scan). Each agent gets: the repo path, the Phase 2 seed candidates, the existing
features (dedup), the existing journeys (dedup, from Phase 1), its single lens
focus, and the per-lens output schema below. This is the one sanctioned parallel
batch of the workflow; everything else stays sequential.
Adaptive lens count (cap 12). Scale the roster to repo size so small repos stay cheap and large repos get full coverage:
< 150 files → ~4 lenses150–500 files → ~8 lenses> 500 files → up to the full 12
Core lenses are ALWAYS cast regardless of size: domain-boundary,
test-mapper, journey-analyst, docs-coverage.Lens roster (up to 12):
| Lens | Looks for |
|---|---|
| domain-boundary | user-facing domains from READMEs + package structure |
| test-mapper | behaviors from test names/structure → feature clusters; fills detectedTestFiles gaps |
| cross-module capability | features spanning multiple directories |
| API/entry-point surface | routes, controllers, CLI commands, events, public exports |
| data-model | schemas, migrations, domain entities |
| journey-analyst | entry points + personas → ordered user paths → draft journeys |
| security-surface | auth/crypto/PII → securityClassification |
| config/integration | external integrations, webhooks, env contracts |
| docs-coverage | README / docs/** near each candidate → detectedDocFiles + gap flags |
| perf/runtime | background jobs, caches, queues → infra features |
| error-handling/observability | logging, metrics, error paths |
| persona-split | refine journeys per persona (admin vs end-user vs API consumer) |
Per-lens output schema. Each agent returns: missed features, merge/split
suggestions, refined boundaries, per-candidate detectedSourceFiles /
detectedTestFiles / detectedDocFiles additions, doc gaps, and draft
journeys (persona + ordered steps referencing candidate slugs — abstract until
apply, since zensu journeys step needs real feature IDs that exist only after
Phase 4).
Consolidate in this main thread (not a subagent). Dedup by slug, reuse the exact existing slug on a match (enables enrichment during apply), union the three detection arrays per slug, and keep grouping feature-level not function-level. Carry the merged draft-journey list forward to Phase 5.
No silent caps. When repo size trims the roster, log (tell the user) how
many lenses ran and which were skipped — a capped scan must read as "capped on
purpose," never as "covered everything." Same rule when a > 500 file repo is
scanned at reduced breadth.
Present the refined candidate table (mark seed vs fan-out origin) plus a draft-journey preview. The user reviews/edits before you submit the scan.
Run zensu ghost scan --product <product-id> --repo-url <url> --branch <branch> --candidates '<json>'. Each candidate carries its three detection arrays — populate all of them, and never omit detectedTestFiles or detectedDocFiles:
{
"slug": "authentication",
"title": "Authentication",
"componentSlug": "auth",
"confidenceScore": 0.8,
"detectedSourceFiles": ["src/auth/login.ts", "src/auth/session.ts"],
"detectedTestFiles": ["src/auth/login.test.ts", "src/auth/session.spec.ts"],
"detectedDocFiles": ["docs/auth.md"],
"securityClassification": "restricted"
}
Pre-submit self-check. Sum detectedTestFiles and detectedDocFiles across all candidates. If the Phase 2 walk surfaced test or doc files but either sum is 0 — or far below what you saw — the per-candidate mapping is broken: STOP and re-glob each candidate's source dirs (steps 6–7) before submitting. Importing source without its tests or docs understates real maturity and skews release gates.
Output: "Scan created with {n} candidates ({x} high, {y} medium, {z} low confidence), {t} tests and {d} docs mapped across candidates. Ready for review."
zensu ghost candidates <scan-id> to load all candidates### High Confidence (>= 0.7) — 12 candidates
# | Slug | Component | Tests | Docs | Source
1 | auth-login | auth | 3 | 1 | 5
2 | user-profile | users | 2 | 1 | 3
### Medium Confidence (0.4-0.7) — 5 candidates
13 | api-middleware | middleware | 0 | 0 | 4
### Low Confidence (< 0.4) — 3 candidates
18 | config-loader | infra | 0 | 0 | 1
Scan the Tests column before approving. If it reads 0 for candidates that clearly own source files, the scan missed test detection — do not approve blindly. Return to Phase 2 step 6, re-glob, and re-create the scan. An all-zero Tests column on a repo that has tests is a detection bug, not a property of the code.
Scan the Docs column too. Same rule: if it reads 0 for candidates whose modules ship READMEs or docs/*.md, doc detection was skipped — return to Phase 2 step 7, re-glob, and re-create the scan. An all-zero Docs column on a repo that has docs is a detection bug, not a property of the code.
zensu ghost batch <scan-id> with --approve and --reject arrays to process all decisions in a single call. Optionally provide a reject reason for rejected candidates.zensu ghost apply <scan-id> --enrich-existing if the product already has features (check Phase 1 feature list). Omit --enrich-existing only for the very first scan on an empty product.zensu ghost apply <scan-id> --enrich-existing — apply matches by slug and attaches the newly detected tests to the existing features, no duplicates. This costs ~2 calls per module (scan + apply) instead of one zensu link test per test file.User journeys are a release gate (journey health), yet a brownfield import never gets
them — close that gap here, after zensu ghost apply, when features have real ZEN IDs.
This mirrors /zensu:bootstrap Step 2.
zensu features list --product <product-id> --compact and build a
slug → ZEN-ID map from the just-applied features.feature_id via the map. Drop any step whose slug was
rejected or not applied, and log what was dropped (no silent omission).zensu journeys suggest --product <product-id> for product context.zensu journeys create --product <product-id> per approved journey (title, slug, journey type,
priority, persona).zensu journeys step <journey-id> --product <product-id> per step (--step-order 1-based, --feature,
--interaction-type ∈ action|navigation|input|validation|output|wait,
--critical).zensu journeys health --product <product-id> <journey-id> on each created journey; report weak links to the user.Build-out baseline (automatic, server-side). Each newly created feature is seated at a
v1"Discovered baseline" automatically by the scan apply — minted backend-side (zensu-monorepo #266), no client step here. Backends predating #266 simply have no baseline; harmless. Features fan out later into deeper revisions and subfeatures.
zensu features list --product <product-id> --compact.detectedDocFiles). Recommend /zensu:implement to author code-grounded docs
for them — ghost-scan links existing docs but never generates new ones
(docs/documentation-guide.md)./zensu:implement for feature implementation/zensu:security-review for security classificationzensu doc claude-md for an updated CLAUDE.mdzensu features create with --status planned
— these are genuinely unbuilt (no v1 baseline yet; they get one at
implement-time). This completes the "built + planned" picture that neither a
pure scan nor a pure bootstrap captures alone.| Command | Phase | Purpose |
|---|---|---|
zensu products list | 1 | Validate product |
zensu features list | 1, 5, 6 | Load existing features (dedup); resolve slug → ZEN-ID after apply |
zensu journeys list | 1, 5 | Load existing journeys (dedup) |
Agent (subagent_type: Explore) | 2b | Parallel read-only analysis lenses (fan-out) |
zensu ghost scan | 3 | Create scan with candidates |
zensu ghost candidates | 1 (resume), 4 | Load candidates |
zensu ghost batch | 4 | Batch approve/reject candidates in one call |
zensu ghost apply | 4 | Apply approved candidates (use --enrich-existing if product has features) |
zensu journeys suggest | 5 | Product context for journey suggestions |
zensu journeys create | 5 | Create a discovered journey |
zensu journeys step | 5 | Add ordered steps linking real feature IDs |
zensu journeys health | 5 | Report weak links on created journeys |
zensu features create | 6 | Hybrid: create planned-but-unbuilt features from a forward plan doc |
zensu doc claude-md | 6 | Update CLAUDE.md (optional) |
Provides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub mkitconsulting/zensu-claude-code --plugin zensu