mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-13 19:06:01 +00:00
feat(skill): add blocking-io-guard — SOP skill for blocking-IO triage and runtime anchors (#3503)
* feat(blocking-io): add changed-lines blocking-IO scanner (L1) * feat(blocking-io): add scan-changed CLI wrapper * feat(skill): add blocking-io-guard developer SOP skill * docs(blocking-io): point contributors at the blocking-io-guard skill * style(blocking-io): apply ruff format to scanner and tests * docs(backend): document changed-lines blocking-IO scanner in CLAUDE.md * feat(skill): add post-fix re-scan check and PR batching policy * refactor(skill): fix SOP step ordering, align template with repo conventions - Move re-scan into an explicit 'apply the fix' step (was wedged after anchor generation while telling you to go back before the anchor) - Renumber steps 0-6; drop undefined 'L1' jargon - Mode A: document that the diff is <base>...HEAD (commit first) - Mode B: prefer make detect-blocking-io + findings JSON file - anchor template: module-level pytestmark per tests/blocking_io convention - CLAUDE.md: fix 'git diff --base' phrasing * fix(skill): catch findings introduced without touching the blocking line Review follow-up: changed-line intersection alone misses the case where a new async caller exposes an old sync helper — the static finding sits on the untouched blocking line, so Mode A returned empty and the SOP stopped on a false 'no blocking-IO surface'. Selection is now a union over the changed files: - findings on added lines of git diff <base>...HEAD (kept: a second identical symbol in an already-flagged function collides on the stable key and only this selection sees it); - findings new versus the merge base, matched by (path, function, symbol) — never line numbers. Base sources are materialized via git show <merge-base>:<path>; files absent at base count every head finding as new. SKILL.md now states the residual same-file-only blind spot (cross-file async callers) instead of treating an empty list as proof of zero exposure, and only requires reading sop-skeleton.md when generalizing to another detector domain. * docs(skill): examples teach test-writing, the teeth check defines the rule All examples in the references/template are filesystem-flavored; make explicit that they are instances, not the SOP's boundary — the same rules apply to every detector category (FILE_IO, HTTP, SUBPROCESS, SLEEP) and acceptance is always red/green teeth, never similarity to an example. Neutralize the template's arrange comment accordingly. * fix(blocking-io): harden changed-lines scanner per review - Dedup the union selection by the stable key (path, function, symbol) instead of dict identity, so a future selector returning copied dicts cannot silently empty the result. - parse_changed_lines now handles any unified diff: context lines advance the new-file counter, \-markers and deletions do not, and the counter resets at each +++ header. Previously correct only for --unified=0. - Add blocking_io_static.scan_source (in-memory scan); base-version comparison no longer round-trips through temp files. - Empty Mode A report now prints the same-file-only reachability caveat at the point of use instead of relying on the SOP text alone. * docs(skill): bound best-effort cleanup when the offload sits in finally Lesson from the #3505 review: the SOP routinely drives 'offload the cleanup branch' transformations, and an awaited cleanup in finally can mask or stall the primary exception. One sentence in Step 2 closes that gap at the point where the fix is written.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# SOP skeleton (generic shape — extraction seam)
|
||||
|
||||
This is the domain-agnostic shape the blocking-IO skill instantiates. It exists
|
||||
so a second detector/gate domain can reuse the flow without copying it. Do not
|
||||
add machinery for that until a second domain actually appears (YAGNI).
|
||||
|
||||
A domain provides:
|
||||
- a **static detector** that can scan a diff (or the whole tree) and emit
|
||||
located candidates,
|
||||
- a **CI gate** that fails when the bad pattern executes,
|
||||
- a **test location** for guard tests,
|
||||
- **good-test rules** for that gate,
|
||||
- a **teeth definition** (how to make the gate fire on purpose).
|
||||
|
||||
Steps:
|
||||
1. **Scope (deterministic):** intersect the diff's added lines with the
|
||||
detector's findings → candidates this change introduced/touched. (Or, in
|
||||
triage mode, take the full finding list ordered by priority.)
|
||||
2. **Judge (router):** per candidate — guard existing fix / fix + guard /
|
||||
no-action / rule (the gate cannot see the primitive).
|
||||
3. **Fix + re-scope (fixes only):** apply the fix, re-run the detector; the
|
||||
fixed candidate must vanish from the findings (match by a stable key, not
|
||||
line numbers). Pattern-level feedback in seconds — complements, never
|
||||
replaces, step 5.
|
||||
4. **Generate:** draft or extend a guard test per the good-test rules, driving
|
||||
the specific branch.
|
||||
5. **Verify teeth:** make the bad pattern happen → gate must fail; restore →
|
||||
gate must pass. A pattern that stays green while genuinely bad is the
|
||||
"rule" signal, not a coverage success.
|
||||
6. **Deliver:** commit the verified guard test; any gate-rule change ships in
|
||||
its own commit with the fails-to-fail evidence attached.
|
||||
|
||||
To add a domain: supply a new fill doc (like `good-anchor-rules.md`) + detector,
|
||||
and promote this file into a parent skill the instances point at.
|
||||
Reference in New Issue
Block a user