# Mechanical-edit prompt contract — runtime edit-plan bridge

This document explains what the ONE-SHOT and BATCH prompt templates
teach the model when the input X is a mechanical-edit task. The
executor-facing schemas are owned by `simplicio-runtime`:
`simplicio.edit-plan/v1` for the plan and `simplicio.edit-result/v1`
for the executor's response. The historical
`simplicio.mechanical-edit/v1` marker remains prompt-level activation
guidance only; it is not a runtime executor schema and this repo must
not fork the runtime edit schemas.
Historical wrapper results named `simplicio.mechanical-edit-result/v1`
must be normalized to `simplicio.edit-result/v1` before review.

The prompt contract was originally tracked through
[`wesleysimplicio/simplicio-runtime#69`](https://github.com/wesleysimplicio/simplicio-runtime/issues/69).
For current runtime compatibility, any richer mechanical-edit planner
envelope must be compiled to `simplicio.edit-plan/v1` before calling
`simplicio edit`.

The change in the prompt is small and pointed: the LLM stops being a
whole-file text rewriter and starts being a planner/reviewer that emits
only strict JSON.

## When the contract activates

The model treats X as a mechanical-edit task when **any** of the
following is true:

- X is wrapped in `[MECHANICAL-EDIT]`.
- The input mentions `schema: "simplicio.mechanical-edit/v1"`.
- The input mentions `schema: "simplicio.edit-plan/v1"`.
- The contract or caller explicitly asks for a JSON edit plan
  conforming to the runtime edit-plan schema.

Outside those triggers, the existing ONE-SHOT artifact rules apply
unchanged — there is no behavior change for non-mechanical-edit tasks.

## What the model outputs

The final executor-facing artifact is a single JSON object that validates
against `simplicio.edit-plan/v1`. **No prose**, no markdown fences, no
sentences before or after. Runtime executable plans use top-level fields
at minimum:

```json
{
  "schema": "simplicio.edit-plan/v1",
  "file": "src/router.ts",
  "expect_sha256": "<optional current file sha256>",
  "operations": [
    {
      "op": "replace",
      "find": "if (req.method === \"GET\") {",
      "with": "if (req.method === \"GET\" || req.method === \"HEAD\") {"
    }
  ]
}
```

If a caller asks for a planner envelope before execution, the prompt may
carry mapper context and validation intent while planning. That envelope
is not sent to the runtime executor unchanged; it must be reduced to the
standard edit-plan shape above before execution. The legacy planner shape
looks like:

```json
{
  "schema": "simplicio.mechanical-edit/v1",
  "task": "<exact goal supplied by the caller>",
  "context": {
    "files": [
      {
        "path": "src/router.ts",
        "anchor": "function handle(req)",
        "anchor_hash": "sha256:...",
        "selected_range": [12, 48]
      }
    ]
  },
  "operations": [
    {
      "op": "replace",
      "file": "src/router.ts",
      "find": "if (req.method === \"GET\") {",
      "value": "if (req.method === \"GET\" || req.method === \"HEAD\") {"
    }
  ],
  "validation": [
    { "kind": "test", "command": "npm run test -- router.spec.ts" }
  ],
  "postconditions": [
    {
      "kind": "regex",
      "file": "src/router.ts",
      "pattern": "req\\.method === \"HEAD\""
    }
  ]
}
```

Notes:

- `context.files[*].anchor_hash` and `selected_range` come straight
  from mapper output. The model re-emits them verbatim; it never
  invents anchors or fabricates hashes.
- `operations[]` is the **smallest safe set** that satisfies `task`.
  Prefer `replace` / `insertAfter` / `insertBefore` / `regexReplace`
  over whole-file overwrites. A whole-file overwrite is only allowed
  when the contract explicitly selects a safe operation for a new or
  intentionally replaced file.
- `validation[]` are mechanical checks a planner/reviewer wrapper should
  run around the runtime edit operation. They are not fields in
  `simplicio.edit-plan/v1`.
- `postconditions[]` are present when semantic correctness can be
  verified mechanically (a regex that must match after the edit, a
  hash that must change, an integer that must be in range).

## Insufficient anchors → context request, never a guess

When mapper context does not give enough anchors / hashes / line ranges
to plan safely, the model emits a plan whose only operation is a
structured **context request**:

```json
{
  "schema": "simplicio.mechanical-edit/v1",
  "task": "<same task>",
  "context": { "files": [{"path": "src/router.ts"}] },
  "operations": [
    {
      "op": "context_request",
      "file": "src/router.ts",
      "around_anchor": "function handle(req)",
      "lines_before": 40,
      "lines_after": 60,
      "reason": "Need to see how req.headers flow into the response builder before patching the method check."
    }
  ],
  "validation": []
}
```

It does **not** paraphrase the file, hallucinate anchors, or fall back
to a free-form rewrite.

## Reviewing `simplicio.edit-result/v1`

After the executor returns a result, the same model reviews it before
declaring success. Runtime results include `schema`, `file`, `changed`,
`operations_applied`, `operations`, `before_sha256`, `after_sha256`, and
the token ledger. The result must satisfy **all** of the following. Any
failure forces a corrected JSON plan rather than a narrative apology:

| Failure | Required response |
|---|---|
| Result payload missing or schema is not `simplicio.edit-result/v1` | Re-issue the plan, unchanged. |
| `operations_applied` is lower than the intended operation count | Issue a corrected plan addressing the failed operation detail. |
| `changed: false` when the task required a content change | Issue a corrected plan unless the no-op was explicitly intended. |
| Wrapper validation failed and no explicit `policy_waiver` is present | Issue a corrected plan that fixes the validation failure (tighter anchor, broader test, missing postcondition). |
| Final `before_sha256` or `after_sha256` missing | Re-issue the plan asking for a valid runtime result. |
| Operation summaries do not match the plan's intent | Issue a corrected plan with the right `operations[].find` / `line`. |

A corrected plan is again a single strict JSON object. **No "I'll try
that again" sentences. No full-file rewrite.**

## Worked corrections

### Invalid anchor

Executor returned `applied: false` with reason
`"anchor not found in src/router.ts"`. The corrected plan re-fetches
broader context:

```json
{
  "schema": "simplicio.mechanical-edit/v1",
  "task": "Add HEAD method to the GET branch",
  "context": { "files": [{"path": "src/router.ts"}] },
  "operations": [
    {
      "op": "context_request",
      "file": "src/router.ts",
      "around_pattern": "method === \"GET\"",
      "lines_before": 8,
      "lines_after": 16,
      "reason": "Anchor 'function handle(req)' was missing in the executor's snapshot; widen the lookup."
    }
  ],
  "validation": []
}
```

### Validation failure

Executor reported `validation[0].passed: false`, the regex
postcondition didn't match. The corrected plan narrows the operation:

```json
{
  "schema": "simplicio.mechanical-edit/v1",
  "task": "Add HEAD method to the GET branch",
  "context": { "files": [{"path": "src/router.ts", "anchor_hash": "sha256:..."}] },
  "operations": [
    {
      "op": "regexReplace",
      "file": "src/router.ts",
      "pattern": "if \\(req\\.method === \"GET\"\\) \\{",
      "replacement": "if (req.method === \"GET\" || req.method === \"HEAD\") {"
    }
  ],
  "validation": [
    { "kind": "test", "command": "npm run test -- router.spec.ts" }
  ],
  "postconditions": [
    {
      "kind": "regex",
      "file": "src/router.ts",
      "pattern": "req\\.method === \"HEAD\""
    }
  ]
}
```

### Final compact review

When everything passes (`applied: true`, all `validation[*].passed`,
final hashes present, ranges match the intent) the model emits a
single JSON object signalling acceptance — also strict, no prose:

```json
{
  "schema": "simplicio.mechanical-edit-review/v1",
  "task": "Add HEAD method to the GET branch",
  "accepted": true,
  "matched_intent": true,
  "verified_postconditions": [
    "regex:req\\.method === \"HEAD\" matched in src/router.ts"
  ],
  "evidence": {
    "final_hashes": { "src/router.ts": "sha256:..." },
    "diff_lines": 2
  }
}
```

## Acceptance & tests

The prompt edits land in
`prompts/agent-runtime-execution-prompt.md` (ONE-SHOT) and
`prompts/agent-runtime-batch.md` (BATCH). Existing contract precedence
rules — `[CLI-6LAYER]` / `[GOAL]` / `[TARGET]` / `[CONTRACT]` /
`[OUTPUT]` — stay above this section: a 6-layer contract that
specifies a non-JSON output shape still wins, exactly as documented.

`tests/test_mechanical_edit_prompt.py` pins the structural guarantees:
the canonical schema name is referenced, the runtime issue is linked,
the activation triggers (`[MECHANICAL-EDIT]` marker, the schema
string, the JSON-plan request phrasing) are all present, and the
refusal checklist (`applied: false`, validation failed, hashes/diff
missing, ranges don't match) is enforced.
