Skip to content

Commit fd53527

Browse files
qqqysQwen-CoderQwen-CoderQwen-Coder
authored
feat(cli): support batch deletion of sessions in /delete (#3733)
* feat(cli): support batch deletion of sessions in /delete Closes #3706 Add multi-select mode to the session picker so /delete can remove multiple sessions at once. Space toggles a checkbox on the cursor item; Enter commits the checked set, falling back to single-select when nothing is checked. The current active session is rendered disabled with a "current — cannot delete" hint and is also stripped defensively before the service call. Core gains a `removeSessions(ids)` batch API that returns `{ removed, notFound, errors }` so the CLI can surface partial failures with a single toast. Co-Authored-By: Qwen-Coder <noreply@alibabacloud.com> * fix(cli): address /delete batch-delete review comments - useSessionPicker: when checked items are all hidden by the branch filter, do not silently fall through to single-deleting the cursor row (data-loss path); stay in multi-select mode instead. - SessionPicker footer: count only the checked-and-visible-and-committable rows so "N selected" matches what Enter would actually delete. - useDeleteCommand: partial-failure toast switches to type=error and surfaces failing session ids (truncated, capped at 3 with overflow) plus the first underlying error message, instead of just an aggregate count masquerading as info. - Docs: fix Tab→Space JSDoc / inline-comment drift across the picker surface (Space is the actual binding). Co-Authored-By: Qwen-Coder <noreply@qwenlm.com> * docs(cli): clarify disabledIds is multi-select-only on session picker Reviewer flagged that disabledIds is silently inert in single-select mode because both its visual dim and Space no-op gate on enableMultiSelect. Spell that out on both prop JSDocs and point future callers at filtering initialSessions for single-select use cases. Co-Authored-By: Qwen-Coder <noreply@alibabacloud.com> * fix(cli): address /delete batch-delete review feedback (round 2) - useSessionPicker: throw when enableMultiSelect is on without onConfirmMulti (footer would otherwise read "N selected" while Enter silently fell through to single-select on the cursor row) - useSessionPicker: single-select Enter fallback now respects disabledIdSet so a stray Enter on the dimmed active-session row no longer closes the dialog and bounces back with an error - useDeleteCommand: stop swallowing the outer catch — log + surface the underlying error message so on-call has something to grep - useDeleteCommand: full-failure branch now mirrors the partial- failure branch (failing ids + first error reason) instead of a generic "Failed to delete sessions." - useDeleteCommand: emit a "Deleting N session(s)..." progress toast before awaiting removeSessions so slow filesystems don't leave the user staring at a closed dialog - sessionService: JSDoc {@link removed}=false → {@link notFound} Tests: regression for the disabled-row single-select fallback, two invariant-throw tests for the picker, a pre-await progress toast test, and align the StandaloneSessionPicker branch-filter tests with main's Ctrl+B-only binding (plain 'B' silently entered search mode after the merge, masking the assertion). Co-Authored-By: Qwen-Coder <noreply@qwen.ai> * test(cli): drop wait-based multi-select picker tests, cover at hook level The Multi-select describe block in StandaloneSessionPicker.test.tsx was 7 ink-rendering tests that all relied on `await wait(N)` to sync with stdin events — the same flaky shape #3978 already purged from the search suite. CI flaked on them once and they're gone for good. Critical invariants are re-asserted at hook level in useSessionPicker.test.tsx (toggleChecked add/remove + no-op on disabled ids), which exercise pure state without keypress sim. Footer-rendering and keypress-driven flows are intentionally left to manual verification rather than carried as wait-based integration tests. Co-Authored-By: Qwen-Coder <noreply@qwen.ai> * test(cli): cover batch delete keyboard and failure paths * fix(cli): address /delete batch-delete review feedback (round 3) - useSessionPicker: Enter now commits *every* checked id (minus disabled), not just the filtered intersection. Filter is a navigation aid; gating the commit on it would silently drop checks the user explicitly made (check A-E, type a query matching only C-E, lose A and B). Order by sessionState.sessions so the receiver sees display order even for filter-hidden items. - SessionPicker: footer count switches from visibleCheckedCount to committableCheckedCount (all checkedIds minus disabled), so it can no longer say "0 selected" while Enter is about to delete three hidden checks. - useDeleteCommand: hoist sampleIds/overflow/firstError/reason above the three-way branch so partial- and full-failure paths can't drift out of sync on a future tweak. - useDeleteCommand: in-flight ref guard wrapped in try/finally drops re-entrant /delete invocations (closeDeleteDialog runs synchronously, so without this the user can re-open /delete and queue an overlapping batch). Guard releases on early return too, otherwise a "only current selected" rejection would lock out the rest of the session. - useDeleteCommand: surface "Current active session skipped." info toast when the picker forwarded the active session, otherwise the progress toast lies about the count. Tests: - useDeleteCommand: full-failure branch (removed=0), re-entrant drop, guard-released-on-early-return, stripped-current info toast - useSessionPicker: hook-level keypress suite covering Space toggle, Space-disabled no-op, Enter→onConfirmMulti, Enter→onSelect fallback, Enter-disabled no-op, Enter commits hidden checks, Enter refuses when every check is disabled. MockStdin pattern cloned from useKeypress.test.ts, no ink rendering and no wait(). Co-Authored-By: Qwen-Coder <noreply@qwen.ai> * chore(cli): trim delete-many comments * fix(cli): guard delete actions during batch delete * test(cli): cover batch delete guard release * fix(cli): address session delete review feedback * fix(cli): address batch delete review follow-ups --------- Co-authored-by: Qwen-Coder <noreply@alibabacloud.com> Co-authored-by: Qwen-Coder <noreply@qwenlm.com> Co-authored-by: Qwen-Coder <noreply@qwen.ai> Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
1 parent faf646b commit fd53527

15 files changed

Lines changed: 1534 additions & 205 deletions

.qwen/skills/codegraph/SKILL.md

Lines changed: 111 additions & 109 deletions
Large diffs are not rendered by default.

.qwen/skills/codegraph/bug-analysis.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ print(result.format_report())
4141

4242
The result object (`BugAnalysisResult`) contains:
4343

44-
| Field | Type | Description |
45-
|-------|------|-------------|
46-
| `issue` | `ParsedIssue` | Parsed issue with extracted paths/funcs/commits |
47-
| `candidates` | `list[RootCauseCandidate]` | Ranked root cause locations |
48-
| `path_matches` | `int` | How many extracted paths matched graph File nodes |
49-
| `semantic_matches` | `int` | How many semantic matches were found |
50-
| `caller_traces` | `int` | How many mentioned functions had traceable callers |
51-
| `analysis_time_ms` | `float` | Total analysis time |
44+
| Field | Type | Description |
45+
| ------------------ | -------------------------- | -------------------------------------------------- |
46+
| `issue` | `ParsedIssue` | Parsed issue with extracted paths/funcs/commits |
47+
| `candidates` | `list[RootCauseCandidate]` | Ranked root cause locations |
48+
| `path_matches` | `int` | How many extracted paths matched graph File nodes |
49+
| `semantic_matches` | `int` | How many semantic matches were found |
50+
| `caller_traces` | `int` | How many mentioned functions had traceable callers |
51+
| `analysis_time_ms` | `float` | Total analysis time |
5252

5353
### Inspecting the Parsed Issue
5454

.qwen/skills/codegraph/patterns.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,37 @@ Run these via `cs.conn.execute(query)`. All queries return lists of tuples.
55
## Structural Queries
66

77
**Who calls a function?**
8+
89
```cypher
910
MATCH (caller:Function)-[:CALLS]->(f:Function {name: 'free_irq'})
1011
RETURN caller.name, caller.file_path
1112
ORDER BY caller.name LIMIT 30
1213
```
1314

1415
**What does a function call?**
16+
1517
```cypher
1618
MATCH (f:Function {name: 'sched_fork'})-[:CALLS]->(callee:Function)
1719
RETURN callee.name, callee.file_path
1820
```
1921

2022
**Transitive callers (up to 3 hops):**
23+
2124
```cypher
2225
MATCH (caller:Function)-[:CALLS*1..3]->(f:Function {name: 'kfree'})
2326
RETURN DISTINCT caller.name, caller.file_path LIMIT 50
2427
```
2528

2629
**Functions in a module:**
30+
2731
```cypher
2832
MATCH (f:Function)<-[:DEFINES_FUNC]-(file:File)-[:BELONGS_TO]->(m:Module)
2933
WHERE m.path_prefix = 'net/core'
3034
RETURN f.name, file.path LIMIT 30
3135
```
3236

3337
**Cross-module calls (e.g. fs → mm):**
38+
3439
```cypher
3540
MATCH (f1:Function)-[:CALLS]->(f2:Function),
3641
(file1:File)-[:DEFINES_FUNC]->(f1),
@@ -42,6 +47,7 @@ ORDER BY calls DESC LIMIT 20
4247
```
4348

4449
**Fan-in and fan-out:**
50+
4551
```cypher
4652
MATCH (caller:Function)-[:CALLS]->(f:Function)-[:CALLS]->(callee:Function)
4753
WHERE f.is_historical = 0
@@ -51,6 +57,7 @@ ORDER BY risk DESC LIMIT 20
5157
```
5258

5359
**Module sizes:**
60+
5461
```cypher
5562
MATCH (f:Function)<-[:DEFINES_FUNC]-(file:File)-[:BELONGS_TO]->(m:Module)
5663
WHERE f.is_historical = 0
@@ -59,6 +66,7 @@ ORDER BY func_count DESC LIMIT 30
5966
```
6067

6168
**Functions by name pattern:**
69+
6270
```cypher
6371
MATCH (f:Function) WHERE f.name STARTS WITH 'irq_'
6472
RETURN f.name, f.file_path LIMIT 20
@@ -72,19 +80,22 @@ RETURN f.name, f.file_path LIMIT 30
7280
## Evolution Queries
7381

7482
**Functions modified by a commit:**
83+
7584
```cypher
7685
MATCH (c:Commit)-[:MODIFIES]->(f:Function)
7786
WHERE c.hash STARTS WITH 'abc123'
7887
RETURN f.name, f.file_path
7988
```
8089

8190
**Commits touching a file:**
91+
8292
```cypher
8393
MATCH (c:Commit)-[:TOUCHES]->(file:File {path: 'kernel/sched/core.c'})
8494
RETURN c.hash, c.message, c.author
8595
```
8696

8797
**Co-changed functions (modified together frequently):**
98+
8899
```cypher
89100
MATCH (c:Commit)-[:MODIFIES]->(f1:Function),
90101
(c)-[:MODIFIES]->(f2:Function)
@@ -94,25 +105,29 @@ ORDER BY co_changes DESC LIMIT 20
94105
```
95106

96107
**Largest commits (most functions changed):**
108+
97109
```cypher
98110
MATCH (c:Commit)-[:MODIFIES]->(f:Function)
99111
RETURN c.hash, c.message, count(f) AS funcs_changed
100112
ORDER BY funcs_changed DESC LIMIT 10
101113
```
102114

103115
**Historical (deleted/renamed) functions:**
116+
104117
```cypher
105118
MATCH (f:Function) WHERE f.is_historical = 1
106119
RETURN f.name, f.file_path LIMIT 30
107120
```
108121

109122
**Backfill progress:**
123+
110124
```cypher
111125
MATCH (c:Commit) WHERE c.version_tag = 'bf'
112126
RETURN count(c) AS backfilled
113127
```
114128

115129
**Most frequently modified files:**
130+
116131
```cypher
117132
MATCH (c:Commit)-[:TOUCHES]->(f:File)
118133
RETURN f.path, count(c) AS commits

.qwen/skills/codegraph/pr-analysis.md

Lines changed: 47 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ Key flags:
7272
## Step 4 — Run the unified PR review pipeline
7373

7474
The authoritative entry point is `pr_review.py` — a unified pipeline that combines:
75+
7576
- Per-PR structural risk scoring (blast radius, test coverage, interface changes, etc.)
7677
- Cross-PR graph analysis (connected components, shared callers, function conflicts)
7778
- A single Markdown report with three prioritized sections
@@ -135,21 +136,21 @@ share the same implementation — prepare via CLI, query via Python works.
135136

136137
**`codegraph pr-review prepare`:**
137138

138-
| Argument | Required | Description |
139-
|----------|----------|-------------|
140-
| `--db` | Yes | Path to the `.codegraph` database directory |
141-
| `--repo` | No | GitHub repository in `owner/repo` format (auto-detected from `git remote`) |
142-
| `--author` | No | Filter PRs by GitHub login |
143-
| `--output` | No | Output directory for reports (default: `./pr_review_output`) |
144-
| `--skip-single-pr` | No | Skip per-PR risk scoring; only cross-PR conflict analysis |
139+
| Argument | Required | Description |
140+
| ------------------ | -------- | -------------------------------------------------------------------------- |
141+
| `--db` | Yes | Path to the `.codegraph` database directory |
142+
| `--repo` | No | GitHub repository in `owner/repo` format (auto-detected from `git remote`) |
143+
| `--author` | No | Filter PRs by GitHub login |
144+
| `--output` | No | Output directory for reports (default: `./pr_review_output`) |
145+
| `--skip-single-pr` | No | Skip per-PR risk scoring; only cross-PR conflict analysis |
145146

146147
**`codegraph pr-review label`:**
147148

148-
| Argument | Required | Description |
149-
|----------|----------|-------------|
150-
| `--db` | Yes | Path to the `.codegraph` database directory |
151-
| `--repo` | No | GitHub repository in `owner/repo` format (auto-detected from `git remote`) |
152-
| `--dry-run` | No | Preview labels and comments without making API calls |
149+
| Argument | Required | Description |
150+
| ----------- | -------- | -------------------------------------------------------------------------- |
151+
| `--db` | Yes | Path to the `.codegraph` database directory |
152+
| `--repo` | No | GitHub repository in `owner/repo` format (auto-detected from `git remote`) |
153+
| `--dry-run` | No | Preview labels and comments without making API calls |
153154

154155
### Report Structure
155156

@@ -160,6 +161,7 @@ The unified report (`pr_review.md`) has three sections:
160161
3. **Part 3 — Conflicting PR Groups**: PRs sharing code/call paths, must be reviewed as a batch
161162

162163
Each PR entry includes:
164+
163165
- Risk level (CRITICAL/HIGH/MEDIUM/LOW/UNKNOWN) with emoji
164166
- Impact scope (peak blast radius, clickable to call graph)
165167
- Key risk factors
@@ -173,23 +175,23 @@ For each PR, the script computes signals at two levels.
173175

174176
### File-level signals
175177

176-
| Signal | How it's computed |
177-
|---|---|
178-
| **Changed files** | `gh pr diff --name-only`, filtered to source extensions (.ts/.tsx/.js/.jsx/.py/.java/.c/.cpp/.h/.go, excluding test files) |
179-
| **Module spread** | Set of top-level `packages/xxx` directories touched |
180-
| **Config/schema files** | File name matches common config patterns across languages (config.ts, settings.ts, settingsSchema.ts, types.ts, *.yaml, *.toml, *.cfg, etc.) |
181-
| **Interface/abstract changes** | Diff lines starting with `+` that match `interface Foo` or `abstract class Foo` |
182-
| **Potential dead code** | Functions in changed files that appear in `cs.dead_code()` (fan_in = 0 in graph) |
178+
| Signal | How it's computed |
179+
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
180+
| **Changed files** | `gh pr diff --name-only`, filtered to source extensions (.ts/.tsx/.js/.jsx/.py/.java/.c/.cpp/.h/.go, excluding test files) |
181+
| **Module spread** | Set of top-level `packages/xxx` directories touched |
182+
| **Config/schema files** | File name matches common config patterns across languages (config.ts, settings.ts, settingsSchema.ts, types.ts, _.yaml, _.toml, \*.cfg, etc.) |
183+
| **Interface/abstract changes** | Diff lines starting with `+` that match `interface Foo` or `abstract class Foo` |
184+
| **Potential dead code** | Functions in changed files that appear in `cs.dead_code()` (fan_in = 0 in graph) |
183185

184186
### Function-level signals (per function in changed files)
185187

186-
| Signal | How it's computed |
187-
|---|---|
188-
| **blast_radius** | `fan_in × fan_out` via Cypher CALLS edge counts |
189-
| **call depth** | Fixed 1-hop and 2-hop queries from known entry points (`main`, `run`, `sendMessageStream`, etc.). Returns 1/2 if reachable, -1 if not. Variable-length path queries (`CALLS*1..N`) are intentionally avoided — they are expensive on large graphs. |
190-
| **test coverage** | Graph CALLS from `.test.` files to this function, with filesystem fallback (same-stem `.test.ts` exists) |
191-
| **new vs modified** | `locate_pr(pr_num)` — returns per-file `New Function` / `Hunk Function` / `Deleted Function` sets; functions in `Hunk Function - New Function` are classified as modified, the rest as new |
192-
| **co-change risk** | `cs.co_change(func_name)` — historically co-changed files absent from PR. **Currently disabled** (`co_change_missing` weight = 0.0) |
188+
| Signal | How it's computed |
189+
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
190+
| **blast_radius** | `fan_in × fan_out` via Cypher CALLS edge counts |
191+
| **call depth** | Fixed 1-hop and 2-hop queries from known entry points (`main`, `run`, `sendMessageStream`, etc.). Returns 1/2 if reachable, -1 if not. Variable-length path queries (`CALLS*1..N`) are intentionally avoided — they are expensive on large graphs. |
192+
| **test coverage** | Graph CALLS from `.test.` files to this function, with filesystem fallback (same-stem `.test.ts` exists) |
193+
| **new vs modified** | `locate_pr(pr_num)` — returns per-file `New Function` / `Hunk Function` / `Deleted Function` sets; functions in `Hunk Function - New Function` are classified as modified, the rest as new |
194+
| **co-change risk** | `cs.co_change(func_name)` — historically co-changed files absent from PR. **Currently disabled** (`co_change_missing` weight = 0.0) |
193195

194196
### Per-PR confidence score
195197

@@ -213,12 +215,12 @@ The PR score is the average per-function score plus file-level bonuses (interfac
213215

214216
### Risk level thresholds
215217

216-
| PR Risk Score | Level |
217-
|---|---|
218-
| ≥ 12 | CRITICAL |
219-
| ≥ 7 | HIGH |
220-
| ≥ 3 | MEDIUM |
221-
| < 3 | LOW |
218+
| PR Risk Score | Level |
219+
| ------------- | -------- |
220+
| ≥ 12 | CRITICAL |
221+
| ≥ 7 | HIGH |
222+
| ≥ 3 | MEDIUM |
223+
| < 3 | LOW |
222224

223225
---
224226

@@ -308,10 +310,10 @@ with open(OUTPUT_FILE, 'w') as fout:
308310

309311
Cross-PR conflicts are detected at three levels of granularity:
310312

311-
| Level | What it detects | How it's detected |
312-
|-------|----------------|-------------------|
313-
| **File-level overlap** | Two PRs modify the same file | Set intersection of `gh pr diff --name-only` results |
314-
| **Function-level overlap** | Two PRs modify the same function (even different lines) | `CHANGES {info: 'hunk'}` edges to the same `Function` node — this powers the automated connected-components detection |
313+
| Level | What it detects | How it's detected |
314+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
315+
| **File-level overlap** | Two PRs modify the same file | Set intersection of `gh pr diff --name-only` results |
316+
| **Function-level overlap** | Two PRs modify the same function (even different lines) | `CHANGES {info: 'hunk'}` edges to the same `Function` node — this powers the automated connected-components detection |
315317
| **Dependency chain overlap** | PR A modifies function X, PR B modifies function Y, and Y calls X — git reports no conflict but merging A may break B's assumptions | Cypher: `MATCH (pr1:PR)-[c1:CHANGES]->(f:Function)-[:CALLS]->(g:Function)<-[c2:CHANGES]-(pr2:PR)` — see usecase3 (manual) queries below |
316318

317319
### When to Use
@@ -358,11 +360,11 @@ cross = CrossPRAnalyzer(cs, repo_dir=REPO_DIR)
358360
rows1, rows2, components = cross.analyze_all(out_dir='/tmp')
359361
```
360362

361-
| Usecase | What it finds | When to use |
362-
|---------|--------------|-------------|
363-
| **usecase1** PR → hunk functions | All functions directly modified by each PR; rendered as vis-network HTML | Quick inventory |
364-
| **usecase2** hunk + fan-in callers | Who calls each modified function (1-hop upstream); rendered as 2-hop vis-network HTML | Blast-radius exposure per PR |
365-
| **usecase3** Connected Components (DSU) | Groups of PRs linked by modifying or deleting the same function | Identifies conflicting PR groups requiring coordinated review |
363+
| Usecase | What it finds | When to use |
364+
| --------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
365+
| **usecase1** PR → hunk functions | All functions directly modified by each PR; rendered as vis-network HTML | Quick inventory |
366+
| **usecase2** hunk + fan-in callers | Who calls each modified function (1-hop upstream); rendered as 2-hop vis-network HTML | Blast-radius exposure per PR |
367+
| **usecase3** Connected Components (DSU) | Groups of PRs linked by modifying or deleting the same function | Identifies conflicting PR groups requiring coordinated review |
366368

367369
### Cypher Queries
368370

@@ -404,15 +406,14 @@ RETURN pr1.id, pr2.id;
404406

405407
### Interpreting usecase3 Results
406408

407-
| Connected Component Size | Meaning | Recommended action |
408-
|--------------------------|---------|-------------------|
409-
| Size 1 (singleton) | PR has no cross-PR dependencies | Can be reviewed and merged independently |
410-
| Size > 1 | PRs in the same component share code or call paths | Review as a batch; see `pr_review.py` Part 3 report |
409+
| Connected Component Size | Meaning | Recommended action |
410+
| ------------------------ | -------------------------------------------------- | --------------------------------------------------- |
411+
| Size 1 (singleton) | PR has no cross-PR dependencies | Can be reviewed and merged independently |
412+
| Size > 1 | PRs in the same component share code or call paths | Review as a batch; see `pr_review.py` Part 3 report |
411413

412414
---
413415

414416
## Index location
415417

416418
- Index directory: `.codegraph/` (repo root, add to `.gitignore`)
417419
- Default output: `/tmp/pr_analysis.txt` (configurable)
418-

0 commit comments

Comments
 (0)