feature
SkillDev toolsTDD-first feature development — crystallise API as a demo test, drive implementation to pass it, run quality stack and progressive review loop. TRIGGER when: user asks to build new functionality, add a capability, or implement a feature in a Python project; phrases: "add X", "implement Y", "build Z feature", "create a new module for". SKIP when: bug fixes (use `/develop:fix`); refactoring without new behaviour (use `/develop:refactor`); non-Python projects; `.claude/` config changes (use `/foundry:manage`).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the feature skill
What this skill tells your AI
The instructions your AI receives, as published by borda/ai-rig in plugins/cc_develop/skills/feature/SKILL.md and read by ahel’s review.
TDD-first feature development. Crystallise API as demo use-case test, drive implementation to pass it, close quality gaps with review, docs, quality stack.
NOT for:
- bug fixes (use
/develop:fix) .claude/config changes (use/foundry:manage(requires foundry plugin))- non-Python projects (JS/TS/Go/Rust) — toolchain assumes pytest; use language-native toolchain instead
- mixed refactor+feature tasks — run /develop:refactor first, then /develop:feature
- Key boundary: end of Step 1 — scope analysis and plan complete, before Step 2 demo test writing.
- Second boundary: end of Step 3 — TDD loop complete, before Step 4 review/close gaps.
- Preserve at boundary 1: dev-dir (checkpoint.md), plan-file, scope from sw-engineer analysis, PYTEST_CMD, --keep items.
- Mid-loop refresh: after each Step 3 TDD cycle contract is rewritten with changed-files + checkpoint.md path — so mid-loop compaction resumes loop (re-run suite for green state) instead of restarting Step 2 demo.
- Preserve at boundary 2: dev-dir, changed files list, test outcomes, PYTEST_CMD.
Agent Resolution
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
_DEV_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_shared_resolve.py" 2>/dev/null) # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
echo "$_DEV_SHARED" > "${TMPDIR:-/tmp}/dev-shared-${CSID}" # cold resolve — every later block warm-reads this
# loads: compaction-contract.md
cat "$_DEV_SHARED/agent-resolution.md"
Contains: foundry check + fallback table. If foundry not installed: substitute each foundry:X with general-purpose per table. Agents this skill uses: foundry:sw-engineer, foundry:qa-specialist, foundry:doc-scribe, foundry:linting-expert, foundry:challenger.
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/task-hygiene.md"
Project Detection
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/runner-detection.md"
Sets $TEST_CMD (full suite) and $PYTEST_CMD (pytest flags). Run at skill start.
Language preflight gate: apply §Language preflight gate from runner-detection.md (loaded above) — sets NON_PY and runs the abort/continue question.
Monorepo language-target gate: if NON_PY empty (Python markers found) but non-Python markers also exist, confirm target language:
# timeout: 5000
MULTI_LANG=false
[ -f "pyproject.toml" ] && [ -f "package.json" ] && MULTI_LANG=true
[ -f "pyproject.toml" ] && [ -f "go.mod" ] && MULTI_LANG=true
[ -f "pyproject.toml" ] && [ -f "Cargo.toml" ] && MULTI_LANG=true
If MULTI_LANG=true: invoke AskUserQuestion — "Monorepo detected (Python + non-Python markers coexist). This skill targets Python/pytest. Is the feature you're building Python-only?" · (a) Yes — Python only — proceed · (b) No — involves non-Python code too — abort; use a language-native toolchain for the non-Python portion. On (b): stop.
Optional --plan <path>: if $ARGUMENTS contains --plan <path> (at any position), read plan file first. Extract Affected files, Risks, Suggested approach — use to populate Step 1 analysis instead of cold codebase exploration. Skip agent feasibility re-check (already done in /develop:plan). Store plan path as PLAN_FILE.
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/preflight-helpers.md"
Execute --plan path extraction; sets $PLAN_FILE.
Checkpoint init: run block below to create .developments/<TS>/ and capture path in $DEV_DIR. Write checkpoint.md inside $DEV_DIR. After each major step (1, 2, 3, 4, 5), append step: N — completed to $DEV_DIR/checkpoint.md. On skill start, check for existing .developments/*/checkpoint.md — if found, offer to resume from last completed step.
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
DEV_DIR=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_run_dir.py" 2>/dev/null)
echo "$DEV_DIR" > "${TMPDIR:-/tmp}/dev-feature-dev-dir-${CSID}"
Flag parsing
Parse flags into actual shell variables (not prose) so downstream blocks see correct values. Persist to temp files for cross-block access (bash state lost between Bash() calls):
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
KEEP_ITEMS=""
if [[ "$ARGUMENTS" =~ --keep[[:space:]]\"([^\"]+)\" ]]; then
KEEP_ITEMS="${BASH_REMATCH[1]}"
fi
echo "$KEEP_ITEMS" > "${TMPDIR:-/tmp}/dev-feature-keep-items-${CSID}"
rm -f .temp/state/skill-contract.md # timeout: 5000
# timeout: 10000
python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_parse_args.py" \
--skill feature --write-files "$ARGUMENTS"
Downstream blocks read back, e.g. IFS= read -r TEAM_MODE < "${TMPDIR:-/tmp}/dev-team-mode-${CSID}" 2>/dev/null || TEAM_MODE=false.
# timeout: 6000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
ISSUE_REF=""
[[ "$ARGUMENTS" =~ --issue[[:space:]]+([^[:space:]]+) ]] && ISSUE_REF="${BASH_REMATCH[1]}"
echo "$ISSUE_REF" > ${TMPDIR:-/tmp}/dev-issue-ref-${CSID}
if [ -n "$ISSUE_REF" ]; then
IFS= read -r REPO_NAME < "${TMPDIR:-/tmp}/dev-upstream-${CSID}" 2>/dev/null || REPO_NAME=""
if [ -n "$REPO_NAME" ]; then
gh issue view "$ISSUE_REF" --repo "$REPO_NAME" 2>/dev/null || echo "⚠ Could not fetch issue $ISSUE_REF from $REPO_NAME — proceeding without issue context"
else
gh issue view "$ISSUE_REF" 2>/dev/null || echo "⚠ Could not fetch issue $ISSUE_REF — proceeding without issue context"
fi
fi
If ISSUE_REF non-empty and issue fetch succeeded: include issue title, body, and labels in Step 1 scope analysis as pre-populated requirements context.
Cross-repo adaptation (when REPO_NAME set) — issue filed against different codebase. After fetching issue, Step 1 scope analysis must also:
- Extract intent from issue — what problem does it solve in abstract terms, not just described implementation details (which assume upstream's structure)
- Check local divergences: run
git log --oneline -10and grep for symbols mentioned in issue; identify where local codebase differs structurally from what issue assumes - Produce adaptation plan: upstream intent → local implementation using local conventions, existing abstractions, and current code structure — never assume upstream approach ports directly
Unsupported flag check — after ALL supported flags extracted (including --issue from block above), scan $ARGUMENTS for remaining --<token> tokens not in supported list. Do NOT include --issue in "unknown" set — it is consumed in second parse block above. Supported: --plan, --team, --worktree, --no-challenge, --challenge, --no-codemap, --codemap, --semble, --accept-no-plan, --issue, --repo, --keep. If truly unknown token found: print ! Unknown flag(s): `--<token>`. then invoke AskUserQuestion — (a) Abort (stop, re-invoke with correct flags) · (b) Continue ignoring (skip unknown flags, proceed). On Abort: stop.
Worktree isolation
loads: worktree-isolation.md
When --worktree set, offload the whole run into an isolated git worktree — before codemap detection or any edit, so codemap scans + all mutations land in the worktree (per-worktree ephemeral index; parallel runs never share one index).
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r WORKTREE_ENABLED < "${TMPDIR:-/tmp}/dev-feature-worktree-${CSID}" 2>/dev/null; [ "$WORKTREE_ENABLED" = "true" ] || WORKTREE_ENABLED=false
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/worktree-isolation.md"
WORKTREE_ENABLED=true → follow §Enter (call EnterWorktree, warm-start codemap). Else skip — run in main tree. Remember the branch for §Exit at Final Report.
Codemap auto-detection — run after flag parsing; reads raw value, normalizes to true/false, writes normalized result so downstream blocks see post-normalization state:
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
CODEMAP_ENABLED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_codemap_gate.py" feature) || exit 1
# codemap: integrated-via-shared
loads: codemap-gates.md
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/codemap-gates.md"
Follow Gate A and Gate B.
Semble preflight — if SEMBLE_ENABLED=true:
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/preflight-helpers.md"
Execute semble preflight if flag set.
Team Mode Branch
Run immediately after flag parsing when TEAM_MODE=true. Runs Step 1 inline (teammates need scope context), then spawns parallel teammates for Steps 2-4. Exit after synthesis.
loads: team-mode.md — gated; ~90% of runs (
--teamabsent) skip the load entirely
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r TEAM_MODE < "${TMPDIR:-/tmp}/dev-team-mode-${CSID}" 2>/dev/null || TEAM_MODE=false
[ "$TEAM_MODE" = "true" ] && cat "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/skills/feature/modes/team-mode.md"
TEAM_MODE=true → execute the loaded protocol now, then exit; do not continue to solo Steps 1-5. TEAM_MODE=false → nothing was loaded; skip to Step 1.
Step 1: Understand purpose and scope
Gather full context before writing any code:
Argument type detection: if
$ARGUMENTSis positive integer (or prefixed with#, e.g.#123), treat as GitHub issue number and fetch withgh issue view. If text, treat as feature description.Issue ID parsing rule:
$ARGUMENTSwith all supported flags stripped is treated as a GitHub issue number only when the entire remaining string is digits (optionally prefixed with#, e.g.123or#123) — matched via^#?[0-9]+$against the full flag-stripped argument, not a leading digit run. A goal like500 error handlingcorrectly stays descriptive text, not issue#500, because the full stripped string isn't digits-only.
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
# strip flags first — mirrors debug/SKILL.md:154-156; CLEAN_ARGS omits --issue/--plan/--keep here
ARGUMENTS_FOR_ISSUE_DETECT=$(echo "$ARGUMENTS" | sed -E 's/--no-challenge|--challenge|--team|--worktree|--no-codemap|--codemap|--semble|--accept-no-plan|--issue[= ]?[^ ]+|--repo[= ]?[^ ]+|--plan[= ]?[^ ]+|--keep[[:space:]]+"[^"]*"//g' | xargs)
if [[ "$ARGUMENTS_FOR_ISSUE_DETECT" =~ ^#?[0-9]+$ ]]; then
python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_develop}/bin/dev_issue_fetch_wrap.py" feature "$ARGUMENTS" # timeout: 6000
ISSUE_FETCH_EXIT=$?
[ "$ISSUE_FETCH_EXIT" -ne 0 ] && echo "⚠ issue_fetch.py failed (exit $ISSUE_FETCH_EXIT) — proceeding without issue context"
fi
If free-text description provided: use Grep tool (pattern <keyword>, glob **/*.py) to search related code. Path hint: use src/ if that directory exists, otherwise search from project root (.).
Codemap target derivation — when feature extends an existing module or modifies an existing function, pre-set TARGET_MODULE/TARGET_FN so codemap-context.md runs caller-impact queries (rdeps module importers, fn-rdeps function callers) before implementation, surfacing who breaks if existing surface changes. Goal may name extension point as module.path or module.path::function:
# timeout: 5000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
if [[ "$ARGUMENTS" == *"::"* ]]; then
_QNAME=$(printf '%s\n' "$ARGUMENTS" | grep -oE '[A-Za-z_][A-Za-z0-9_.]*::[A-Za-z_][A-Za-z0-9_]*' | head -1)
TARGET_MODULE="${_QNAME%%::*}"
TARGET_FN="${_QNAME##*::}" # bare fn — codemap-context.md builds module::fn
elif [[ "$ARGUMENTS" =~ ([A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)+) ]]; then
TARGET_MODULE="${BASH_REMATCH[1]}" # dotted module extension
TARGET_FN=""
else
TARGET_MODULE="" # net-new — only central baseline runs
TARGET_FN=""
fi
export TARGET_MODULE TARGET_FN
echo "$TARGET_MODULE" > ${TMPDIR:-/tmp}/dev-feature-target-module-${CSID} # persist — reloaded by rdeps block (bash state lost between Bash() calls)
echo "$TARGET_FN" > ${TMPDIR:-/tmp}/dev-feature-target-fn-${CSID}
Pure net-new feature (no existing module/function named) → both empty → only
centralbaseline runs, which is correct: nothing to compute caller impact against yet.
Module-importer impact — when CODEMAP_ENABLED=true and TARGET_MODULE set, run rdeps for modules that import extension target, so implementation accounts for downstream importers before changing surface:
# timeout: 6000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r CODEMAP_ENABLED < "${TMPDIR:-/tmp}/dev-feature-codemap-enabled-${CSID}" 2>/dev/null || CODEMAP_ENABLED="false"
IFS= read -r TARGET_MODULE < "${TMPDIR:-/tmp}/dev-feature-target-module-${CSID}" 2>/dev/null || TARGET_MODULE="" # re-derive — bash state lost between Bash() calls
if [ "$CODEMAP_ENABLED" = "true" ] && [ -n "$TARGET_MODULE" ] && command -v codemap-py >/dev/null 2>&1; then
codemap-py query --timeout 5 rdeps "$TARGET_MODULE" --top 10 --exclude-tests 2>/dev/null || true
fi
If CODEMAP_ENABLED=true or SEMBLE_ENABLED=true (codemap normalized by bin/codemap_resolve.py; semble verified by preflight-helpers.md §Semble preflight):
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/codemap-context.md"
Follow enabled sections (codemap block if CODEMAP_ENABLED, semble companion if SEMBLE_ENABLED). Skip entirely if both flags false.
Spawn foundry:sw-engineer agent to analyse codebase and produce:
- Purpose: what problem does feature solve, and for which users?
- Scope: which files and modules likely change (entry points, data models, tests)?
- Compatibility: does feature touch public API? Require deprecation? Need backward-compat shims?
- Reuse opportunities: existing utilities, base classes, patterns, abstractions new code can extend instead of duplicate
- Risks: edge cases, performance implications, integration points needing careful handling
- Scope challenge: Right problem? Simpler alternatives? What already exists that could extend instead of build from scratch?
- Complexity smell: if proposed change touches 8+ files or introduces 2+ new classes/modules, flag explicitly — scope may need narrowing before proceeding
Complexity classification: classify as small (≤3 files, single concern), medium (4–7 files, or 1 new module), or large (8+ files, 2+ new modules, or public API change).
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/plan-inline.md"
§Inline Plan Generation Protocol. Apply using feature context from Skill contexts table. On proceed: set PLAN_FILE=<path>; continue to Step 2. On small complexity or ACCEPT_NO_PLAN=true: skip and continue to Step 2.
Present analysis summary before proceeding.
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_SHARED < "${TMPDIR:-/tmp}/dev-shared-${CSID}" 2>/dev/null || _DEV_SHARED="" # timeout: 5000
[ -z "$_DEV_SHARED" ] && _DEV_SHARED="plugins/cc_develop/skills/_shared"
cat "$_DEV_SHARED/premise-grounding.md"
§Premise Grounding Gate. Apply using feature context from Skill contexts table.
Source Verification (optional — when using external APIs or version-sensitive libraries)
Skip if feature calls no external library APIs — no new framework features, no third-party SDK methods, no stdlib functions changed in recent Python version.
Trigger: feature calls external library API — new framework feature, third-party SDK method, or stdlib function changed in recent Python version.
DETECT → FETCH → CITE pipeline:
-
DETECT — read
pyproject.tomlorrequirements*.txtfor exact version and output:STACK DETECTED: - <library> <exact-version> (from pyproject.toml) → Fetching official docs for the relevant API. -
FETCH — use WebFetch to retrieve specific relevant docs page (not homepage). Source priority: official docs > official changelog/migration guide > web standards (MDN). Never cite Stack Overflow, blog posts, or AI training data.
If WebFetch fails (network unavailable, site down): skip source verification entirely. Proceed to Step 2. Note in Final Report: "Source verification skipped — WebFetch unavailable."
-
CITE — when implementing, embed comment with source URL and key quoted passage:
# Docs: https://docs.example.com/v2/api/method # "The recommended pattern for X is Y" (v2.1 docs) -
Conflict — if docs describe pattern conflicting with how codebase currently uses library:
CONFLICT DETECTED: Existing code uses <old pattern>. <library> <version> docs recommend <new pattern> for this use case. Options: A) Use the documented pattern (may require updating existing call sites) B) Match existing code (works but not idiomatic for this version) → Which approach?
Challenger gate
Decision — three states (default is NOT "skip": it runs on substantial features and auto-skips only small ones):
--no-challenge(CHALLENGE_ENABLED=false) → skip gate entirely, any size.- else
--challenge(IFS= read -r CHALLENGE_FORCED < "${TMPDIR:-/tmp}/dev-challenge-forced-${CSID}" 2>/dev/null || CHALLENGE_FORCED=false=true) → always run, even on a small feature. - else default → run when feature is substantial (multi-file, ≳50 lines, or adds any new public API — common case for a feature); auto-skip when small (single file, ≲50 lines, no new public API).
Both flags exist because they cover opposite regimes: --no-challenge suppresses gate on substantial features where it would otherwise fire; --challenge forces it on small features where it would otherwise auto-skip.
Spawn foundry:challenger with scope analysis from Step 1 (purpose, scope, risks, approach):
"Review implementation approach and scope identified in Step 1. Challenge across all 5 dimensions: Assumptions, Missing Cases, Security Risks, Architectural Concerns, Complexity Creep. Apply mandatory refutation step."
Parse result:
- Blockers found → STOP. Present findings. Don't proceed to Step 2 until user resolves each blocker or explicitly accepts risk.
- Concerns only → surface as advisory section before demo test; continue.
- No findings / all refuted → proceed.
# boundary 1: after scope analysis, before demo/edit (compaction-contract.md)
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
IFS= read -r _DEV_DIR < "${TMPDIR:-/tmp}/dev-feature-dev-dir-${CSID}" 2>/dev/null || _DEV_DIR=""
IFS= read -r _PLAN_FILE < "${TMPDIR:-/tmp}/dev-plan-file-${CSID}" 2>/dev/null || _PLAN_FILE=""
IFS= read -r _KEEP < "${TMPDIR:-/tmp}/dev-feature-keep-items-${CSID}" 2>/dev/null || _KEEP=""
IFS= read -r _PYTEST_CMD < "${TMPDIR:-/tmp}/dev-pytest-cmd-${CSID}" 2>/dev/null || _PYTEST_CMD=""
_PRESERVE="dev-dir=$_DEV_DIR, plan-file=${_PLAN_FILE:-none}, pytest-cmd=$_PYTEST_CMD"
[ -n "$_KEEP" ] && _PRESERVE="$_PRESERVE; user-keep: $_KEEP"
mkdir -p .temp/state # timeout: 5000
{
echo "## Active Skill Contract"
echo "- skill: develop:feature · phase: demo+edit (after scope analysis and plan)"
echo "- run-dir: $_DEV_DIR"
echo "- preserve: $_PRESERVE"
echo "- next: write demo test (Step 2) → TDD loop (Step 3) → review (Step 4)"
} > .temp/state/skill-contract.md
Step 2: Write a demo use-case
Before crystallising API, surface non-obvious design decisions:
ASSUMPTIONS I'M MAKING:
- [assumption about API shape, e.g. "returning a list not a generator"]
- [assumption about caller context, e.g. "called once per batch, not per item"] → Correct me now or I'll proceed with these.
Don't proceed to demo if any assumption would materially change API shape.
Crystallise intended API contract before any implementation. Choose form based on scope:
Choosing demo form: use inline doctest for simple functions/methods with minimal setup; use example script for features requiring external state, multiple steps, or side effects.
Unit function / simple API -> inline doctest (doctest in method docstring; must fail against current code).
Complex feature (setup required, side effects, multi-step flow) -> minimal example script examples/demo_<feature>.py; shows intended API end-to-end; becomes formal pytest test once implementation complete and API stable (end of Step 3).
Both forms must:
- Use exact API feature will expose (function name, signature, return type)
- Show happy-path end-to-end flow user would first reach for
- Fail or error against current code (feature doesn't exist yet)
Gate: demo must fail or error.
<module> is a substitution token — resolve actual module file path (e.g. src/mypackage/feature.py) into shell variable $MODULE_PATH before executing these blocks. Do NOT execute with literal <module>.py string — bash would interpret < as stdin redirect from a file named module>.py.
# Resolve MODULE_PATH before this block — e.g.:
# MODULE_PATH=$(find src/ -name '*.py' | head -1)
# timeout: 30000
export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}"
$PYTEST_CMD --collect-only --doctest-modules $MODULE_PATH -q 2>&1 | tail -5; COLLECT_EXIT=${PIPESTATUS[0]}
if [ "$COLLECT_EXIT" -eq 5 ]; then
echo "⚠ GATE FAIL: no demo tests collected — demo file missing or doctest malformed"
GATE_EXIT=1
elif [ "$COLLECT_EXIT" -ne 0 ]; then
echo "⚠ Cannot collect doctests — check module for import errors (collect exit $COLLECT_EXIT)"
GATE_EXIT=1
fi
echo "${GATE_EXIT:-0}" > ${TMPDIR:-/tmp}/dev-feature-gate-exit-${CSID}
echo "$COLLECT_EXIT" > ${TMPDIR:-/tmp}/dev-feature-collect-exit-${CSID}
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 27
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
feature-borda- Source
- github.com/borda/ai-rig