# Loopspec

> Compile an informal task description into a deterministic Harness-Driven Delegation Specification with an automated validation loop, for execution by a DeepSeek coding sub-agent. Fires when the user ends a request with `/loopspec`, or says "compile this into a delegation spec", "harness loop", "convergence loop", "spec this for deepseek", "make this a validation loop", "delega con loop di validazione", "trasforma in specifica per deepseek".

- Skill: `tommasobbianchi/loopspec` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tommasobbianchi/loopspec`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tommasobbianchi/loopspec/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: tommasobbianchi (https://skillmd.com/u/tommasobbianchi)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tommasobbianchi/loopspec

---


# /loopspec — informal intent → harness-driven delegation specification

Takes everything the user wrote **before** the literal token `/loopspec` and compiles it
into a machine-checkable delegation contract for a DeepSeek worker. The contract's
defining property: **the harness, not the model, decides when the task is done.**

## 1. Trigger & parsing

Input syntax: `<informal task description> /loopspec [flags]`

- Everything preceding `/loopspec` is the requirement source. Text after it is flags only.
- If `/loopspec` appears with nothing before it in the current message, use the most recent
  substantive user request in the conversation as the requirement source.
- Never ask clarifying questions before emitting the spec. Ambiguity that survives is
  recorded verbatim under **Open Bindings** in §1 of the output and given a stated default.

### Flags

| Flag | Effect |
|---|---|
| `--dispatch` | After emitting the spec, hand it to a DeepSeek worker (see §7). Default: emit only. |
| `--max-iter=N` | Hard iteration ceiling in the convergence loop. Default 6. |
| `--lang=py\|ts\|rs\|go` | Force the toolchain instead of detecting it. |
| `--f2p=<path>` | Name the fail-to-pass test target explicitly. |
| `--dry` | Emit the spec with detected commands but do not run anything, not even detection. |

## 2. Pre-compilation probe (mandatory, cheap, read-only)

The commands in §3 of the output must be **real commands for this repo**, never
placeholders. Before emitting, probe the cwd — one batched call, read-only:

```bash
ls pyproject.toml setup.cfg package.json Cargo.toml go.mod Makefile 2>/dev/null
sed -n '1,40p' pyproject.toml 2>/dev/null | grep -iE 'ruff|black|mypy|pytest|flake8'
jq -r '.scripts // {} | to_entries[] | "\(.key): \(.value)"' package.json 2>/dev/null
ls tests test spec __tests__ 2>/dev/null
git -C . rev-parse --abbrev-ref HEAD 2>/dev/null
```

Resolution order for each command slot:
1. An explicit script/target in the project manifest (`package.json` scripts, `Makefile`
   target, `[tool.*]` section) — always wins.
2. The tool the manifest declares as a dependency.
3. The language default (`ruff check .` / `pytest -q`, `eslint .` / `vitest run`,
   `cargo clippy -- -D warnings` / `cargo test`, `golangci-lint run` / `go test ./...`).

If nothing resolves, emit `MISSING: <slot>` in that slot and list it as a blocking
Open Binding. **Never invent a command that was not observed.**

`deepseek-harness` is the wrapper the worker runs the sequence under. If the repo has no
harness entry point, the harness *is* the ordered command sequence plus its exit codes —
say so in the spec rather than referencing a binary that does not exist here.

## 2b. Command validation (mandatory before emission)

A resolved command is not yet a valid one. Every string that will appear in §3 of the
output must be proven to exist and to parse, in the target project, before the spec ships.
Cheap, non-mutating, no test actually executed:

```bash
<linter> --version                      # tool installed and on PATH
<linter> --help >/dev/null              # invocation form accepted by this version
<test runner> --collect-only -q <F2P target>   # pytest: target path resolves and imports
<test runner> --listTests <F2P target>         # jest/vitest equivalent
<harness entry> --help                  # harness binary exists, if one is referenced
```

Rules:
- **Pin the interpreter, never trust PATH.** Resolve the project's own environment
  (`.venv/bin/python -m pytest`, `node_modules/.bin/vitest`, `poetry run`, `uv run`) and emit
  that. A second installation of the same tool on PATH is the normal case, not the exception,
  and it usually has a different version and a different dependency set — the PATH copy then
  dies at import time with an error that reads like a code defect and is not.
- A command whose tool is not installed is `MISSING`, not a suggestion to install it.
  Declared in the manifest's dev extras but absent from the environment still counts as
  absent: check the disk, not the declaration.
- **Read the collected count, never the probe's exit code.** Verified on pytest 8:
  `--collect-only -q` on a file with no tests prints `no tests collected` and **exits 0**.
  Gating on `$?` blesses a target that can never fail, and every termination gate then
  passes vacuously against an empty diff. Require a printed count `>= 1`; anything else is a
  **broken F2P binding** and a blocking Open Binding. Same trap in jest (`--listTests` on a
  non-matching pattern) and `go test ./...` on a package with no `_test.go`.
- Flags must match the *installed* version, not the one you remember. `--collect-only`,
  `-D warnings`, `--listTests` and friends differ across major versions; the `--help` probe
  is what settles it.
- Quote and escape exactly as the worker will paste it. A path with a space, a glob the
  shell would expand, a `-k` expression with parentheses — all belong inside quotes in the
  emitted string, not left for the worker to fix.
- No command chaining in a §3 slot. One slot, one invocation, one exit code — `&&` collapses
  two failure modes into one and destroys the gate's diagnostic value.

## 2c. Lint baseline — the gate is a delta, never an absolute

`linter_errors == 0` is unsatisfiable on almost every real repo, and a spec that demands it
forces the worker to choose between violating the scope limit and never terminating. Measured
on a live 722-test project: **1232 pre-existing ruff errors repo-wide, 25 in the single target
file.** Neither is the worker's to fix, and both would be attributed to its diff.

So capture a lint baseline before the first EDIT and gate on the difference:

```bash
<linter> check --output-format=json <in-scope files> \
  | python3 -c "import json,sys,collections;print(dict(collections.Counter(x['code'] for x in json.load(sys.stdin))))"
```

Compare the post-edit fingerprint against the baseline as a **multiset of `(file, code)`**,
not as a total and not by line number — lines shift under any edit, codes do not. The gate is:
no code absent at baseline appears afterwards, and no code's count rises. A *drop* is fine
and needs no comment.

Scope the linter invocation to the §1 writable files. Linting the whole repo imports 1200
diagnostics the diff never touched into a gate the diff is judged by.

Emit this in the spec as an explicit gate restatement, with the baseline fingerprint inline
so the worker can evaluate it without re-deriving anything.

## 3. Output template — emit verbatim, filling every bracket

```markdown
# DELEGATION SPECIFICATION: HARNESS-DRIVEN VALIDATION LOOP

## 1. TARGET GOAL
- **Functional Objective:** [precise, unambiguous, testable requirements]
- **Target Files / Scope:** [explicit writable paths; everything else read-only]
- **Open Bindings:** [ambiguity + the default assumed; "none" if none]

## 2. HARNESS ENVIRONMENT & GROUND TRUTH
- **Harness Interface:** [harness entry point, or the ordered command sequence] is the
  sole deterministic oracle of task status.
- **Fail-to-Pass (F2P) Criteria:** [test target defining the feature/bugfix — must fail now, pass at the end]
- **Pass-to-Pass (P2P) Criteria:** [existing suite guarding regression — must stay green]
- **Test Integrity Constraint:** Modifying, mocking, skipping, xfailing or otherwise
  tampering with harness test suites or oracle fixtures is prohibited and invalidates the run.

## 3. VERIFICATION COMMANDS
1. Lint & Static Analysis: `[exact command]`
2. Harness Patch Evaluation: `[exact command]`
3. Targeted Test Execution: `[exact command]`

## 4. CONVERGENCE LOOP (FORMAL EXECUTION PROTOCOL)
Iterate until termination criteria hold, ceiling [N] iterations:
1. **EDIT:** apply scoped modifications to in-scope source files.
2. **EXECUTE:** run the §3 sequence in order, in the sandbox.
3. **PARSE:** consume the structured payload — failing assertions, stack traces, linter records.
4. **PATCH:** derive root cause from the parsed diagnostics and refine the edit.
On ceiling without convergence: stop, do not report success, return the last diff plus the
unresolved failure set.

## 5. TERMINATION CRITERIA (BOOLEAN GATES)
Finalize IF AND ONLY IF all gates hold, each backed by captured stdout:
- [ ] `harness_exit_code == 0`
- [ ] `fail_to_pass_status == ALL_PASSED`
- [ ] `pass_to_pass_regressions == 0`
- [ ] `new_linter_diagnostics == 0` — post-edit `(file, code)` multiset over the §1
      writable files introduces no code absent from the baseline and raises no count.
      Baseline fingerprint: `[inline it here]`. Absolute zero is NOT the gate; the
      repo's pre-existing diagnostics are out of scope and fixing them is a scope violation.

## 6. GUARDRAILS & EXECUTION CONSTRAINTS
- **Zero-Assumption Rule:** completion is never declared without verifiable stdout and exit codes.
- **Context Preservation:** no raw log dumps; extract diagnostic diffs and failure traces only.
- **Blast Radius Limitation:** minimal diffs, strictly inside the §1 scope; no drive-by refactors,
  no dependency additions, no reformatting of untouched lines.
- **Oracle Supremacy:** the harness verdict is final and overrides the worker's own
  judgement. A test the worker believes is wrong is still the specification; it reports
  the disagreement and stops, it does not edit the test to agree with its code.
- **Baseline Obligation:** before the first EDIT, run §3 once and record the baseline —
  a P2P test already red at baseline is not a regression you caused; report it, do not fix it silently.
```

## 4. Compilation rules

- **Objective:** rewrite intent as an observable postcondition. "make the parser handle
  nested quotes" → "`parse()` returns the unescaped inner string for input `a "b \"c\"" d`
  instead of raising `ValueError`."
- **Scope:** name files. A scope of "the codebase" defeats the whole gate; if the target
  file is genuinely unknown, that is an Open Binding and the first loop iteration is a
  read-only localisation pass.
- **F2P:** if no failing test exists yet, the F2P criterion is *"author `<path>::<test_name>`
  first, confirm it fails against unmodified source, then implement."* Test-first is the
  only way the oracle can distinguish a fix from a no-op.
- **P2P:** the pre-existing suite, minus anything red at baseline.
- Test directories are read-only **except** the single F2P test file when the F2P criterion
  is authoring it. State that exception explicitly; leave nothing to inference.

## 5. Invariants

The spec is invalid, and must be regenerated rather than shipped, if any holds:
- a §3 command is a placeholder, a guess, or `MISSING`;
- a §3 command was never put through the §2b validation probes;
- the F2P target collects zero tests (the gate would pass vacuously);
- F2P and P2P name the same target (nothing then guards regression);
- scope includes a test path without the §4 authoring exception;
- a termination gate is unmeasurable by the §3 commands as written;
- the lint gate demands absolute zero on a repo whose baseline is non-zero.

## 6. Emission

Write the spec to `.claude/loopspec/<slug>.spec.md` (create the directory), print the path plus a
three-line summary: objective, scope, gate count. The spec file is the artefact handed to the
worker — the conversation carries the pointer, not the payload.

## 7. Dispatch (`--dispatch` only)

Hand the spec file to your DeepSeek runner (e.g. `deepseek-tui`, or any non-interactive coding
agent CLI), one worker, in a git worktree so the diff stays reviewable:

```bash
git worktree add -b loopspec/<slug> .worktrees/loopspec/<slug>
deepseek exec --auto --yolo --cd .worktrees/loopspec/<slug> \
  "$(cat .claude/loopspec/<slug>.spec.md)"
```

The worker never sees this conversation, so the spec file must be self-contained — that is
the point of writing it to disk first.

**A worktree has no environment.** `git worktree add` copies tracked files only, so the
project venv / `node_modules` is absent and every §3 command fails on a fresh worktree.
Before dispatching: link it (`ln -s ../../.venv .venv`) and then *prove the source resolves
inside the worktree*, because an editable install points at the main checkout and a worker
can otherwise spend its whole budget editing files that the tests never import:

```bash
.venv/bin/python -c "import <pkg>; print(<pkg>.__file__)"   # must be under the worktree
.venv/bin/python -m pytest -q     # baseline must reproduce here, not just in the main tree
```

Copy the spec into the worktree too — the worker reads it from its own cwd.

**After the worker returns, re-run every gate yourself.** A worker's claim of success is
evidence, not proof. In order:

1. `git status --porcelain` and `git diff --stat` — is the change surface the §1 scope?
2. `git diff -- tests/ pyproject.toml` — **must be empty** apart from the F2P file. A green
   report next to a modified test or a loosened warning filter is a failed run.
3. Re-run §3 slots 2 and 3. Compare the pass count against the baseline, not against zero.
4. Recompute the lint fingerprint and diff it against the baseline multiset.
5. **Prove the F2P test was really red.** This is the one gate a worker cannot self-certify,
   and the one most worth checking: stash the source fix and run the F2P target alone.
   ```bash
   git stash push -q -- <source files> && .venv/bin/python -m pytest -q <f2p target>; git stash pop -q
   ```
   It must FAIL. If it passes without the fix, the test asserts nothing and every other gate
   is meaningless.

