Skip to content

Commit 8c991c0

Browse files
committed
Split patch rebasing to reference, fence Block F, add changed_files_pass gate
TAG=agy CONV=3520bd51-372a-49aa-9bdb-dfd183f66aff Change-Id: I27ee34cccd2732ee767accccc04b68ccd28d79fb
1 parent 4f8c104 commit 8c991c0

7 files changed

Lines changed: 196 additions & 188 deletions

File tree

‎AGENTS.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,29 @@ If the hook modifies files, stage the changes and amend your commit.
1616
Blocks A/B/C/D/E/F/G are wrapped in ```` ``` ```` fences and must stay
1717
**character-identical** across all skills. Never insert content *inside* their
1818
fences — add notes *after* the closing ```` ``` ```` instead.
19+
20+
## Reference files
21+
22+
Skills may split on-demand content into `references/` subdirectories (e.g.
23+
`mantis-calibrate/references/calibration_rules.md`). Before committing, verify
24+
that every `references/*.md` link target in a `SKILL.md` file exists on disk and
25+
is tracked by git (`git ls-files --error-unmatch <path>`).
26+
27+
### Extraction criteria
28+
29+
Extract a block into a `references/` file only if ALL three hold:
30+
31+
1. **Off the fail-closed safety-critical path** — the extracted content is not
32+
the sole authority for a security-critical invariant (e.g. the
33+
`VERIFIED_SECURE => reattack_status = failed_to_bypass` gate).
34+
2. **Restates no invariant** — the extracted content does not restate any rule
35+
that is also stated in the SKILL.md (no invariant may appear in both a
36+
SKILL.md and its reference; if a rule must be referenced, state it once and
37+
point to it).
38+
3. **Clean fail-safe fallback** — if the reference cannot be loaded, the skill
39+
falls back to safe behavior without the reference (e.g. patch rebasing falls
40+
back to fresh patch generation, verified by Block G).
41+
42+
`mantis-patch/references/patch_rebasing.md` qualifies (pure mechanics, no
43+
invariant restated, fails safe to Phase-1). Do not extract content that restates
44+
a crown-jewel invariant — trim it inline instead.

‎mantis-architecture/SKILL.md‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ a canonical, interlinked Markdown Knowledge Base (`workspace/kb/`).
4646
current KB was last built against; absent on a first/legacy KB).
4747
- `workspace/.mantis_state.json` → `changed_files` and `changed_files_status`
4848
(written by mantis-plan's Block E; consumed by the scoped KB invalidation in
49-
step 0b outcome 3. Absent on the Stage-2 invocation before planning — the
50-
guardrail falls back to full rebuild. Present on the Stage-15 invocation
51-
after planning — enables scoped invalidation).
49+
step 0b outcome 3. Present from pass 2 on, but may be stale (written in a
50+
prior pass) — the scoped path checks `changed_files_pass` against
51+
`state.pass_number` and falls back to full rebuild if they differ.)
5252
- **Writes**:
5353
- Markdown files under `workspace/kb/` (`architecture.md`,
5454
`entities/[component_name].md`, `vulnerabilities/[CWE-ID].md`, `index.md`).
@@ -173,13 +173,16 @@ VCS):**
173173
unstamped / legacy. Choose full or scoped:
174174
- **Scoped invalidation (Phase 2 incremental efficiency):** If
175175
`changed_files_status` is known (not UNKNOWN) AND the KB already has a
176-
`KB_SNAPSHOT` stamp (KB_ID was non-empty, just different), attempt a
177-
SCOPED rebuild: only invalidate KB entries whose source files are in
178-
`changed_files`, plus their parent-rollup dependents (KB entities that
179-
import/reference the changed files). Re-derive ONLY those entries from
180-
`CODE_ROOT`; carry forward all other KB entries unchanged (they were
181-
built against the same code, just a different snapshot ID). Re-stamp
182-
`KB_SNAPSHOT: CUR` on every (re)written file.
176+
`KB_SNAPSHOT` stamp (KB_ID was non-empty, just different) AND
177+
`changed_files_pass` equals the current `state.pass_number` (the diff
178+
is from THIS pass, not a stale prior pass — absent or different →
179+
treat as UNKNOWN → full rebuild below), attempt a SCOPED rebuild: only
180+
invalidate KB entries whose source files are in `changed_files`, plus
181+
their parent-rollup dependents (KB entities that import/reference the
182+
changed files). Re-derive ONLY those entries from `CODE_ROOT`; carry
183+
forward all other KB entries unchanged (they were built against the
184+
same code, just a different snapshot ID). Re-stamp `KB_SNAPSHOT: CUR`
185+
on every (re)written file.
183186
- **Parent-rollup (2-hop, matching plan's fan-out):** When
184187
invalidating a KB entry for changed file F, also invalidate any KB
185188
entry that REFERENCES F directly (1-hop) AND any entry that

‎mantis-patch/SKILL.md‎

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -434,28 +434,12 @@ Execute the patching and verification stage as follows:
434434
is an optimization for the common case where a prior fix still applies with
435435
minor line-number shifts.
436436

437-
- **How:** Locate the prior pass's `patch_diff` from the archived finding
438-
(the finding JSON in `workspace/archive/findings_pass_<N-1>/`).
439-
`patch_diff` is a unified DIFF, not a whole file. Reconstruct the three
440-
3-way-merge inputs as SCRATCH copies (never edit the archived copies or
441-
either snapshot in place):
442-
- `base` = the PRIOR snapshot's unpatched version of the file, copied to
443-
a scratch path (requires the prior snapshot dir on disk — see the
444-
reachability check; if absent, fall back to fresh generation).
445-
- `ours` = a copy of `base` with the archived `patch_diff` applied
446-
(`patch <base_copy> < patch_diff`, or `git apply`). This reconstructs
447-
the prior PATCHED file. If the diff does not apply cleanly to `base`,
448-
fall back to fresh generation.
449-
- `theirs` = the CURRENT snapshot's unpatched version of the same file,
450-
copied to a scratch path. Then run
451-
`git merge-file -p <ours> <base> <theirs> > <result>` — the `-p` flag
452-
writes the merged result to stdout so it does NOT overwrite `<ours>` in
453-
place (argument order is ours, base, theirs). If `git` is unavailable,
454-
use `diff3 -m <ours> <base> <theirs> > <result>` or any 3-way merge
455-
tool that writes to a SEPARATE output. If the merge produces no
456-
conflict markers, `<result>` is the current file with the fix rebased
457-
onto it. If no 3-way merge tool is available, fall back to fresh patch
458-
generation (Phase-1 behavior).
437+
- **How:** If ALL reachability conditions (a)-(d) below hold, read
438+
[3-Way Patch Rebasing](references/patch_rebasing.md) with your
439+
file-reading tool for the 3-way merge mechanics (`base`/`ours`/`theirs`
440+
scratch copies, `git merge-file` / `diff3` invocation, conflict-marker
441+
handling). On ANY uncertainty or if the reference cannot be loaded, fall
442+
back to fresh patch generation (Phase-1 behavior).
459443
- **When to use (reachability by OBSERVABLE state, not a flag the patcher
460444
cannot read):** attempt rebasing ONLY when ALL hold: (a) the prior pass's
461445
snapshot directory AND the file's prior unpatched version actually EXIST
@@ -485,19 +469,13 @@ Execute the patching and verification stage as follows:
485469
read from `workspace/.mantis_state.json` (the same state object read for
486470
`active_snapshot`).
487471

488-
- **When rebasing SUCCEEDS** (clean 3-way merge): use the rebased patch as
489-
the starting point for verification. Still run Block G (unpatched baseline
490-
\+ post-patch gate) to confirm the rebased patch is correct. Mark the
491-
finding with a history note `patch-rebased-from: pass_<N-1>`.
492-
493-
- **When rebasing FAILS** (merge conflict, file deleted, file renamed beyond
494-
recognition, or the patched function no longer exists): fall back to
495-
generating a fresh patch from scratch (Phase-1 behavior). Never use a
496-
conflicting or ambiguous rebased patch.
497-
498-
- **Guardrail:** On ANY uncertainty, fall back to Phase-1 patch generation.
499-
Never apply a rebased patch that has unresolved conflicts or that touches
500-
code unrelated to the original fix.
472+
- **When rebasing SUCCEEDS / FAILS / Guardrail:** See
473+
[3-Way Patch Rebasing](references/patch_rebasing.md) for the success (clean
474+
merge → use rebased patch, still run Block G, history note
475+
`patch-rebased-from: pass_<N-1>`), failure (merge conflict / file deleted /
476+
renamed → fall back to fresh patch generation), and guardrail (on ANY
477+
uncertainty, fall back to Phase-1; never apply a rebased patch with
478+
unresolved conflicts or unrelated changes).
501479

502480
- **Unpatched-Baseline Re-run in `--reattack` (Phase 2):** When the
503481
`@mantis-reproduce --reattack` sub-agent is re-attacking a patch on a NEW
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# 3-Way Patch Rebasing (Phase 2 Incremental Efficiency)
2+
3+
> **Load trigger:** This reference is loaded when `/mantis-patch` determines
4+
> that ALL reachability conditions for 3-way patch rebasing are satisfied (see
5+
> `mantis-patch/SKILL.md` Step 2 for the inline reachability gate). The
6+
> procedure below is the 3-way merge mechanics and success/failure handling.
7+
>
8+
> **Safe fallback:** If the agent forgets to load this reference, the correct
9+
> behavior is to fall back to fresh patch generation (Phase-1 behavior). This is
10+
> documented in the SKILL.md guardrail.
11+
12+
## How: 3-Way Merge Mechanics
13+
14+
- **How:** Locate the prior pass's `patch_diff` from the archived finding (the
15+
finding JSON in `workspace/archive/findings_pass_<N-1>/`). `patch_diff` is a
16+
unified DIFF, not a whole file. Reconstruct the three 3-way-merge inputs as
17+
SCRATCH copies (never edit the archived copies or either snapshot in place):
18+
- `base` = the PRIOR snapshot's unpatched version of the file, copied to a
19+
scratch path (requires the prior snapshot dir on disk — see the reachability
20+
check; if absent, fall back to fresh generation).
21+
- `ours` = a copy of `base` with the archived `patch_diff` applied
22+
(`patch <base_copy> < patch_diff`, or `git apply`). This reconstructs the
23+
prior PATCHED file. If the diff does not apply cleanly to `base`, fall back
24+
to fresh generation.
25+
- `theirs` = the CURRENT snapshot's unpatched version of the same file, copied
26+
to a scratch path. Then run
27+
`git merge-file -p <ours> <base> <theirs> > <result>` — the `-p` flag writes
28+
the merged result to stdout so it does NOT overwrite `<ours>` in place
29+
(argument order is ours, base, theirs). If `git` is unavailable, use
30+
`diff3 -m <ours> <base> <theirs> > <result>` or any 3-way merge tool that
31+
writes to a SEPARATE output. If the merge produces no conflict markers,
32+
`<result>` is the current file with the fix rebased onto it. If no 3-way
33+
merge tool is available, fall back to fresh patch generation (Phase-1
34+
behavior).
35+
36+
## When Rebasing SUCCEEDS / FAILS / Guardrail
37+
38+
- **When rebasing SUCCEEDS** (clean 3-way merge): use the rebased patch as the
39+
starting point for verification. Still run Block G (unpatched baseline +
40+
post-patch gate) to confirm the rebased patch is correct. Mark the finding
41+
with a history note `patch-rebased-from: pass_<N-1>`.
42+
43+
- **When rebasing FAILS** (merge conflict, file deleted, file renamed beyond
44+
recognition, or the patched function no longer exists): fall back to
45+
generating a fresh patch from scratch (Phase-1 behavior). Never use a
46+
conflicting or ambiguous rebased patch.
47+
48+
- **Guardrail:** On ANY uncertainty, fall back to Phase-1 patch generation.
49+
Never apply a rebased patch that has unresolved conflicts or that touches code
50+
unrelated to the original fix.

‎mantis-plan/SKILL.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,9 @@ Execute the planning stage as follows:
196196
live tree). Write the computed `changed_files` (array of repo-relative
197197
paths) and `changed_files_status` (`COMPUTED` or `UNKNOWN`) back to
198198
`workspace/.mantis_state.json`, then use them for the rest of the stage.
199-
Use the following to know which files changed since the previous pass:
199+
Also write `changed_files_pass` = the current `pass_number` from state,
200+
so consumers can detect a stale (prior-pass) diff. Use the following to
201+
know which files changed since the previous pass:
200202

201203
CHANGED-SINCE-PREVIOUS: run in the LIVE repository root (NOT
202204
SNAPSHOT_ROOT). CUR = current commit/revision; PREV = snapshot_history

0 commit comments

Comments
 (0)