# code-completion-NO-RECOVERY-LOOP

> ABLATION VARIANT — RECOVERY LOOP REMOVED. This is the doctest-driven code-completion skill with the iterative Recovery Loop pattern ablated out. Everything else (Orchestration: the staged pipeline, task routing, 5-tier classification, multi-file progressive disclosure; and the Quality Gates) is preserved exactly as in the full skill. Use this skill whenever you are given a Python code file with missing code and asked to complete it. It generates doctests as a validation oracle, generates the code, runs the doctests ONCE to record a pass/fail signal, and then delivers the first candidate. It does NOT diagnose failures and it does NOT iterate or repair. Trigger for: - Any prompt containing a Python code file where code is missing or truncated - Requests like "complete this function", "fill in the missing code", "implement this" - JSON records with a "prompt" field and a "metadata" field with either "function_name" (function body task) or "line_no" (line completion task) - Any RepoEval-style code completion benc

- Skill: `ibm/code-completion-no-recovery-loop` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add ibm/code-completion-no-recovery-loop`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ibm/code-completion-no-recovery-loop/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: IBM (https://skillmd.com/u/ibm)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ibm/code-completion-no-recovery-loop

---


# Code Completion with Doctest-Driven Validation — NO RECOVERY LOOP (Ablation A)

> **What is ablated:** the Recovery Loop pattern. In the full skill, Step 9 writes
> the candidate to a temp file, runs the doctest runner, diagnoses failures,
> applies a targeted fix, and repeats up to `MAX_RECOVERY_ITERATIONS` times. In
> this variant Step 9 runs the doctest runner **exactly once** and records the
> result. There is **no diagnosis, no fix, and no re-run**. Whatever the first
> candidate produced is what gets delivered.
>
> **What is preserved:** Orchestration (the staged recipe, task routing, the
> 5-tier classification, the multi-file reference structure) and the Quality Gates
> (Steps 5 and 10). This isolates the marginal contribution of the Recovery Loop:
> comparing this variant against the full skill measures exactly what the iterative
> repair buys.

This skill completes missing Python code — either a full function body or the
remainder of a truncated line — using a test-first approach: generate doctests that
define the contract, generate code that fulfils it, then validate once.

The order still matters. Doctests before code forces you to commit to what the
function should *do* before deciding how it does it. The difference from the full
skill is that a misunderstanding surfaced by the single validation pass is
**reported, not repaired** — there is no loop to fix it.

## Folder structure

```
code-completion-NO-RECOVERY-LOOP/
├── SKILL.md                          ← you are here
├── run_doctest.py                    ← executes doctests against candidate code (ONE pass)
├── doctest_generator.md              ← formatting rules, tier strategy, generation patterns
├── single_validation.md             ← the single-pass validation step (replaces recovery_loop.md)
└── injection.md                      ← final assembly and delivery format
```

Load each reference file at the step that calls for it.

---

## Understanding your inputs

You will receive a JSON record in one of two shapes. The single metadata key that
differs between them is how you tell the tasks apart — check it explicitly, never
infer from the prompt shape.

**Function body completion** — the prompt ends right after a function's docstring
closing `"""` (or after the signature `:` if there is no docstring). The body is
entirely absent. Generate the complete indented body.

**Line completion** — the prompt ends mid-line. Generate only the characters that
complete that one line. Nothing more — no newline, no next line.

**Routing rule:** `"function_name"` in metadata → FUNCTION_BODY task.
`"line_no"` in metadata → LINE task. These are mutually exclusive.

---

## Configuration

| Parameter | Default | What it controls |
|---|---|---|
| `DOCTEST_SCOPE` | `"targeted"` | `"targeted"` = target function only; `"file"` = also generate for functions the target calls internally |
| `INJECT_DOCTESTS` | `true` | Whether passing doctests stay in the final output permanently |

> Note: `MAX_RECOVERY_ITERATIONS` and `BEST_ATTEMPT_ON_FAILURE` from the full skill
> are intentionally absent — there is no loop and no across-iteration best-attempt
> tracking in this variant. The single validation pass is always one attempt.

---

## The workflow

Steps run in strict order. Each produces something the next depends on.

```
Step 1  → Analyse the file
Step 2  → Route to task type
Step 3  → Load additional context (only what is needed)
Step 4  → Classify the target function
Step 5  → Pre-generation quality gate
Step 6  → Generate doctests            [→ doctest_generator.md]
Step 7  → Validate doctest structure
Step 8  → Generate first candidate code
Step 9  → Single validation pass       [→ single_validation.md]   (NO recovery loop)
Step 10 → Post-generation quality gate
Step 11 → Inject and deliver           [→ injection.md]
```

---

### Step 1 — Analyse the file

Read the entire `prompt` field before doing anything else. Build a mental model of
the codebase so that whatever you generate fits naturally into it.

As you read, note:
- Every import statement and what it provides
- Module-level variables and constants
- Class names, their methods, and inheritance relationships
- Other function names visible in the prompt and what they do
- The naming style in use (snake_case, camelCase — infer from existing code)
- How existing functions handle errors: raise, return None, return a sentinel value?
- What types existing functions return
- Any decorators on the target function
- The exact docstring of the target function, verbatim, if present

Stop here if the prompt is empty or unparseable. Report the error rather than
proceeding with no context.

---

### Step 2 — Route to task type

Check the `metadata` keys:

- `"function_name"` present → **FUNCTION_BODY** task. Record the function name and
  `metadata["lineno"]` as the line where the body starts.
- `"line_no"` present → **LINE** task. Record `metadata["line_no"]` as the line
  being completed.
- Neither present → stop. Report that the task type cannot be determined.

For **FUNCTION_BODY**: generate everything from the first indented line of the body
through the final return or expression. Match the file's indentation (4 spaces
standard). Do not alter the signature or the docstring.

For **LINE**: generate only the characters that come after the last character of the
prompt on that same line. No leading newline. No additional lines.

---

### Step 3 — Load additional context

The prompt is your primary source. Before loading anything else, exhaust what it
already tells you. Only go further if the target function references names that do
not appear anywhere in the prompt and cannot be inferred from the imports.

Never load the repository's test files. Those are the external ground truth that
runs after delivery. Loading them contaminates the completion.

---

### Step 4 — Classify the target function

Before generating any doctests, classify the target function into the tier that best
describes what it does. The tier determines what kind of doctests are possible.

| Tier | Type | Signal | Doctest strategy |
|---|---|---|---|
| 1 | Pure / Deterministic | No external calls, same output every time | Full input → output examples |
| 2 | Contract-Testable | Non-deterministic output but testable type/shape/range | Test `isinstance`, `len`, bounds |
| 3 | Setup-Assisted | Needs temp object, class instantiation, or asyncio.run | Set up context inline in doctest |
| 4 | Error-Path Only | Calls DB, API, network, filesystem | Test only input validation and error raises |
| 5 | Untestable | Even error paths need live external state | Document with TODO, skip doctest |

**Special case for LINE tasks:** First determine what syntactic context the
truncated line sits in:
- Inside a function body → classify that function using the tier table above
- Inside an import block (`from x import (`) → Tier 5, no doctest possible
- Module-level assignment or constant → Tier 5, no doctest possible
- Class attribute declaration → Tier 5, no doctest possible

For Tier 5 contexts in LINE tasks, skip doctest generation entirely and proceed
directly to Step 8. Record that validation was skipped and why.

Read `doctest_generator.md` for detailed guidance on each tier's generation
patterns before proceeding to Step 6.

---

### Step 5 — Pre-generation quality gate

Before writing any doctest or any code, confirm you can answer these questions. If
you cannot answer one, make the assumption explicit and record your confidence level
(high / medium / low).

- What does this function do? (source: docstring, function name, call sites)
- What input types does it accept?
- What does it return, and in what type?
- What exceptions should it raise, and under what conditions?
- Are all imports this implementation will need already present in the prompt?
- For LINE tasks: what syntactic construct is the truncated line completing?

> Note: in the full skill, a wrong assumption here could still be caught and
> repaired by the recovery loop. In this variant there is no such safety net — a
> wrong assumption here will be carried straight through to delivery. The quality
> gate is therefore the only pre-delivery check on intent. Treat it accordingly.

---

### Step 6 — Generate doctests

Read `doctest_generator.md` now.

Generate doctests appropriate for the tier you identified in Step 4. The goal is to
define the function's contract before implementing it — what goes in, what comes
out, what gets raised.

For **Tier 1–2 FUNCTION_BODY tasks**: generate 2–6 doctests covering the typical
happy path, at least one edge case, and any documented exception.
For **Tier 3 FUNCTION_BODY tasks**: generate setup inline in the doctest block.
For **Tier 4 FUNCTION_BODY tasks**: generate doctests only for input validation and
error-raise paths.
For **LINE tasks inside a function** (Tier 1–3): generate a doctest for the
containing function that exercises the line being completed.

All expected output values must be concrete and deterministic.

---

### Step 7 — Validate doctest structure

Before running anything, verify the doctests are syntactically correct. Check every
doctest block for:
- `>>> ` prefix with exactly one space after the arrows
- `... ` prefix on continuation lines with exactly one space
- Expected output on the very next line after `>>>` — no blank line between them
- `<BLANKLINE>` used wherever expected output contains a blank line
- Exception format exactly: `Traceback (most recent call last):` then `    ...` then
  `ExceptionType: message`
- No `...` wildcard in expected output unless `# doctest: +ELLIPSIS` is on the
  same `>>>` line

Fix any formatting problems before proceeding.

---

### Step 8 — Generate first candidate code

Generate the completion using everything gathered in Steps 1–7.

For **FUNCTION_BODY tasks**: generate the full body, properly indented; stay
consistent with existing imports; honour observed naming and error-handling
patterns; keep consistent with the expected outputs committed to in Step 6; do not
alter the signature or docstring.

For **LINE tasks**: generate only the remainder of the truncated line, starting at
the exact character position where the prompt ends; no leading whitespace or
newline; end at the natural conclusion of the syntactic construct; do not generate
the next line.

---

### Step 9 — Single validation pass  (NO RECOVERY LOOP)

Read `single_validation.md` now.

Write the candidate code to a temporary file alongside the generated doctests, then
run the doctest runner **once**:

```bash
python run_doctest.py /tmp/completion_candidate.py
```

Read the structured output and **record** it: STATUS, how many doctests passed, and
any failure details. That is the entire step.

**This variant does NOT:**
- diagnose the failure class,
- make a targeted fix,
- re-run the runner,
- iterate up to a maximum number of attempts,
- track a best attempt across iterations.

The first candidate from Step 8 is the delivered code regardless of whether the
single validation pass passed or failed. The runner output is captured as a
reported signal only — it does not change the completion.

For Tier 5 / LINE instances there is no runnable oracle, so the runner is skipped
and only a syntax check (`ast.parse` / `python -c`) is performed — same as the full
skill.

---

### Step 10 — Post-generation quality gate

Before delivering anything, verify:
- Does the completion cover only what was missing — no changes to the existing prompt?
- Is indentation consistent throughout (no mixed tabs/spaces, correct depth)?
- For LINE tasks: does the completion attach cleanly to the last character of the
  prompt with no spurious leading space or newline?
- Are there syntax errors in the completion?
- Does the completion reference any names not in scope? Flag but do not block.

Block on syntax errors. Flag but proceed on scope warnings.

---

### Step 11 — Inject and deliver

Read `injection.md` now.

Assemble the final output:
- The original prompt, unchanged
- The completion appended at the correct position
- If `INJECT_DOCTESTS = true`: doctests that PASSED the single validation pass
  embedded in the docstring. Doctests that FAILED the single pass are replaced with
  a TODO comment (a known-failing doctest is never injected).

Deliver in this structure:

```
task_id:          <from metadata>
task_type:        FUNCTION_BODY or LINE
status:           VALIDATED_PASS  (single pass: all doctests passed)
                  VALIDATED_FAIL  (single pass: one or more doctests failed — delivered anyway)
                  SKIPPED_NO_ORACLE (Tier 5 / LINE — syntax check only)
doctests_passing: <n passing> / <n total generated>
completion:       <the generated code only — not the full file>
full_output:      <complete prompt + completion with passing doctests embedded>
```

Then tell the user, in plain language: what tier the function was, how many doctests
were generated, the result of the single validation pass, and — if it failed — what
the failure was (reported, not fixed, since this variant has no recovery loop).

---

## A worked example

**Input (function body task):** `factorial(n)`, Tier 1.

**Step 6 doctests:** `factorial(0) -> 1`, `factorial(5) -> 120`,
`factorial(-1) -> ValueError`.

**Step 8 candidate:**
```python
    if n < 0:
        raise ValueError("n must be non-negative")
    if n == 0:
        return 1
    return n * factorial(n - 1)
```

**Step 9 (single pass):** Runner reports 3/3 passed. Recorded. (Had it reported a
failure, the same candidate would still be delivered — there is no loop to repair it.)

**Step 11 output:**
```
task_id:          example/1
task_type:        FUNCTION_BODY
status:           VALIDATED_PASS
doctests_passing: 3 / 3
```

---

## What this skill does not do

- Run the developer-written ground truth tests
- Modify the function signature or existing docstring prose
- **Iterate, diagnose, or repair after the single validation pass** (ablated)
- Load repository test files

