From e4b8b7645235517960850197c74fb8704ab40bd9 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Tue, 3 Nov 2026 21:49:31 +0800 Subject: [PATCH] chore: add issue/PR templates, PR check, AGENTS.md and shared agent skills (#1043) Adds a PR template (linked issue, what and why, how it was verified, review focus), a minimal reproduction field on the bug form and required acceptance criteria on the feature form, and CONTRIBUTING.md with the issue and PR rules. A PR check labels needs-info and leaves one comment when the issue link or the verification section is missing; it only reads the title and description. AGENTS.md is now tracked, with CLAUDE.md linking to it, and covers layout, rules, commands, the checks CI runs, pitfalls and parallel worktrees. The verify and issue-fix skills are shared under .agents/skills and linked from .claude/skills; the rest of .claude/ stays local. --- .agents/skills/issue-fix/SKILL.md | 91 ++++++++++++++++ .agents/skills/verify/SKILL.md | 57 ++++++++++ .claude/skills/issue-fix | 1 + .claude/skills/verify | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 6 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 10 ++ .github/pull_request_template.md | 17 +++ .github/workflows/pr-check.yml | 69 ++++++++++++ .gitignore | 10 +- AGENTS.md | 118 +++++++++++++++++++++ CLAUDE.md | 1 + CONTRIBUTING.md | 35 ++++++ 12 files changed, 413 insertions(+), 3 deletions(-) create mode 100644 .agents/skills/issue-fix/SKILL.md create mode 100644 .agents/skills/verify/SKILL.md create mode 120000 .claude/skills/issue-fix create mode 120000 .claude/skills/verify create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/pr-check.yml create mode 100644 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 CONTRIBUTING.md diff --git a/.agents/skills/issue-fix/SKILL.md b/.agents/skills/issue-fix/SKILL.md new file mode 100644 index 000000000..00c20c214 --- /dev/null +++ b/.agents/skills/issue-fix/SKILL.md @@ -0,0 +1,91 @@ +--- +name: issue-fix +description: Triage and fix a GitHub issue (or a user-reported bug) end to end — reproduce, find the root cause, generalise it to every sibling case, fix the class rather than the instance, verify, and open one PR. Use when given an issue URL/number or asked to fix a reported bug and open a PR ("看看这个 issue", "修一下这个 bug", "提 pr"). +--- + +# Fix an issue: from one case to the whole class + +The reported case is one sample of a defect class. A fix that only makes the +screenshot go away is not done: the next question is always "does this break for other +languages / platforms / entry points too?". Answer that before anyone asks. + +## 1. Read and reproduce + +- `gh issue view --comments` (repo `l0ng-ai/openidea`). Read attached logs/screenshots + closely: OS, language, toolchain versions, exact error text. +- Locate the code path and confirm the claim in code, with `file:line` evidence. + Say plainly whether it is a real bug, a missing feature, or user environment. +- Reproduce locally when possible (`fixtures/` projects, `/verify` for runtime). If the + reporter's platform (often Windows/Linux) can't be reproduced here, say so up front. +- Do not ask for a generic environment/log checklist before trying the flow and inspecting the code. + Record the attempt and result; ask only for a fact that actually blocks the next diagnostic step. + Missing fields, limited time, or unavailable automation are not by themselves reasons to ask the reporter. +- Treat a maintainer-confirmed bug as an investigation task even if local reproduction fails. Distinguish + “not reproduced here” from “not a bug”, and do not require the reporter to prove its existence again. + +## 2. Name the root cause as a class + +Write one sentence of the form " is missing/wrong, so any hits it", +not " is broken". Typical classes in this codebase: + +- Per-language config handled for one language only (`languages/*.toml`, `ide-sdk`). +- Platform gaps: paths, executables, shells, `.cmd`/`.exe`, separators — macOS-only code. +- Toolchain/version discovery: one probe location, no version check, no rescan. +- Two features each implementing half a flow and never joined (feature isolation → + should go through an action/service). +- State not reset on project switch / window reuse; events not scoped per window. +- Failure surfaced as raw stderr/stack trace or retried until "crashed" instead of a + clear status with an action. +- UI: same primitive styled differently in N places. + +## 3. Sweep for siblings + +Search systematically, not by memory: + +- Same pattern elsewhere: `git grep` the function, the config key, the error string, + the hard-coded path/flag. Check every `languages/*.toml` entry and every platform branch. +- Same flow for other languages/runtimes (Java ↔ Kotlin/Gradle, Python ↔ venv/uv, + Node ↔ Deno/Bun, Go, Rust …): would they fail the same way? +- Other callers/entry points of the same flow (menu, context menu, gutter, shortcut, CLI arg). +- Neighbouring failure modes of the same mechanism (not installed / wrong version / + installed but crashes / slow start / installed mid-session). + For broad sweeps spawn one Explore agent with the class description, not the instance. + +## 4. Decide the fix at the right altitude + +- Fix the mechanism once (shared helper, action, config hook) so siblings are fixed + by construction; don't patch each call site. +- Respect `AGENTS.md` rules (crate boundaries, no cross-feature imports, `IdeResult`, + UTF-16). If the right fix breaks a rule, pick the rule-respecting design and say why. +- Compare with how established IDEs solve it when relevant; prefer the established answer. +- Present: root cause, sibling table, proposed fix, what's deferred. When the user leaves + the choice to you, go ahead without asking again. + +## 5. Implement and test the class + +- Tests cover the class: table-driven over languages/platforms/states, not just the + reported input. Include the reporter's case verbatim. +- Run the scoped checks (`pnpm vitest run `, `cargo nextest run -p `), then + `/verify` in the app for UI or runtime behaviour. + +## 6. PR + +- One branch `fix/`, one PR unless the user asks otherwise; `Fixes #` in the body. +- Don't stage untracked IDE-state dirs (`.ideal/`, `.openidea/`) or screenshots. +- PR body follows `.github/pull_request_template.md`: `Closes #`, then + **What and why** (user-visible change + root cause at class level, 2–4 sentences), + **How it was verified** (commands run and results, siblings fixed/deferred, what could break and how + it was checked, what was not verified, e.g. "Windows not tested"; for UI changes the before/after + screenshot paths from `/verify`), **Review focus** (optional). Short and plain. + +## Report format + +Reply in the user's language: + +``` +Root cause: +Sibling sweep: +| Location / case | Affected? | Action | +Fixed / not covered (why) / not verified (platform) +PR: +``` diff --git a/.agents/skills/verify/SKILL.md b/.agents/skills/verify/SKILL.md new file mode 100644 index 000000000..2ca0d5a08 --- /dev/null +++ b/.agents/skills/verify/SKILL.md @@ -0,0 +1,57 @@ +--- +name: verify +description: Build, launch and drive the OpenIDEA desktop app through the Tauri MCP bridge to verify a change at runtime. +--- + +# Verify OpenIDEA in the running app + +## Launch + +- Run the app from the worktree that holds the change: `pnpm tauri dev` in the background, logging + into your task's scratch subdirectory. Use `pnpm dev:app` instead when your checks call + `window.__TAURI__` from `execute_js`. The wrapper gives each worktree its own app identity, data, + settings and Vite port, so other worktrees and the installed app don't interfere. If it reports a + session already running for this worktree, reuse that one. +- Once the app is up, `pnpm dev:status` lists this worktree's MCP bridges. Pass the reported + `appIdentifier` (the port) on every `mcp__tauri__*` call. The MCP server is shared between + sessions: never `driver_session stop` without an `appIdentifier`, and re-check `document.title` / + `location.href` before acting if anything looks off. +- Make sure the binary contains the change: a session already running for this worktree may be an + older build. Restart it after Rust changes; frontend changes reload through Vite. +- After the app restarts, `driver_session start` again for your port (a stale session says "Already connected"). +- If the `pnpm tauri dev` process exits while the app window stays open, Vite is gone and any webview + reload shows an empty `#root`. Stop that app and start the session again. +- Stop everything you started when done and nothing else; never use process-name kills. + +## Driving + +- `manage_window focus` before screenshots / timing-sensitive steps: a background window throttles timers + (debounced LSP / VCS refreshes stall, `setTimeout` promises in `execute_js` time out) and native + screenshots come back black. +- Settings is a separate native window `settings-main`; pass `windowId`, focus it before screenshotting. +- CodeMirror / xterm ignore MCP `type`. Focus the element, then `document.execCommand('insertText', false, text)`. + For xterm Enter, dispatch a `keydown` (`key: 'Enter', keyCode: 13`) on `.xterm-helper-textarea`. +- MCP clicks arrive with `event.detail === 0`; CodeMirror treats them as line-select clicks. Set the caret + through the view instead: `document.querySelector('.cm-content').cmTile.view`. +- MCP `text` strategy can resolve to a hidden node; prefer coordinates (CSS px = image px × 0.9) or CSS selectors. +- CSS-module edits often trigger a full page reload in dev: dialogs close, Run sessions and terminal + scrollback reset, and each reload spawns a fresh shell. Re-open what you were testing. + +## Evidence + +- For UI or interaction changes, capture `before.png` (on `main` or with the fix reverted) and + `after.png` at the same state and window size, into your task's scratch subdirectory. Multi-step + flows: a numbered series (`after-1.png`, `after-2.png`, ...). +- Report the absolute paths; they go into the PR's **How it was verified** section and the triage + acceptance entry. Never commit screenshots to the feature branch. + +## Fixture + +`fixtures/` has sample projects (go-app, rust-app, ts-app, kotlin-gradle, polyglot). Copy one into +your scratch directory before editing it, and don't commit the IDE state it creates (`.openidea/`). + +## Flows worth driving + +⌘⇧O Goto File · ⌘⇧F Find in Files (preview scroll + highlight) · ⌘⇧A Find Action · editor edit/undo/autosave · +Terminal (Unicode, venv) · Commit view at the default narrow width · Problems · Run → Stop · Project view +New / Delete (nested `a/b.txt` names) · ⌘, Settings search (+ Keymap filter) · Git Log on a repo without commits. diff --git a/.claude/skills/issue-fix b/.claude/skills/issue-fix new file mode 120000 index 000000000..e632043f9 --- /dev/null +++ b/.claude/skills/issue-fix @@ -0,0 +1 @@ +../../.agents/skills/issue-fix \ No newline at end of file diff --git a/.claude/skills/verify b/.claude/skills/verify new file mode 120000 index 000000000..326bc3f85 --- /dev/null +++ b/.claude/skills/verify @@ -0,0 +1 @@ +../../.agents/skills/verify \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b7008354c..35e26f55f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -42,6 +42,12 @@ body: 3. ... validations: required: true + - type: textarea + id: reproduction + attributes: + label: Minimal reproduction + description: A link to a small public repository, or the smallest files/snippet that trigger the problem. Optional, but it's the fastest way to get a fix. + placeholder: https://github.com/you/repro-project - type: textarea id: expected attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index bc9ad7844..ac1c90377 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -27,6 +27,16 @@ body: description: How you'd like it to work. Mockups or examples from other tools are welcome. validations: required: true + - type: textarea + id: acceptance + attributes: + label: Acceptance criteria + description: How we'll know it's done. One observable behavior per line. + placeholder: | + - [ ] Right-clicking a test class shows "Run with Coverage" + - [ ] Coverage results open in the Coverage tool window + validations: + required: true - type: textarea id: alternatives attributes: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..836b8710b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ +Closes # + +## What and why + + + +## How it was verified + + + +## Review focus + + diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 000000000..d1a74b7ee --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,69 @@ +name: PR check +on: + # pull_request_target so the check can label PRs from forks. It never checks out + # or runs PR code: it only reads the PR title and description. + pull_request_target: + types: [opened, edited, reopened, ready_for_review] + +permissions: + issues: write + pull-requests: write + +concurrency: + group: pr-check-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + check: + if: github.event.pull_request.user.type != 'Bot' && !github.event.pull_request.draft + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + const LABEL = 'needs-info'; + const MARKER = ''; + // Docs, CI and pure refactors may skip the linked issue. + const NO_ISSUE_TYPES = /^(docs|ci|build|chore|refactor|style|test)(\(.+\))?!?:/; + + const body = (pr.body || '').replace(//g, ''); + const problems = []; + + const linksIssue = + /\b(close[sd]?|fix(e[sd])?|resolve[sd]?)\s*:?\s+(#\d+|https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/issues\/\d+)/i.test(body); + if (!linksIssue && !NO_ISSUE_TYPES.test(pr.title)) { + problems.push('Link the issue this PR resolves, e.g. `Closes #123`.'); + } + + const verified = body.match(/^##+\s*How it was verified\s*$([\s\S]*?)(?=^##+\s|(?![\s\S]))/im); + if (!verified || !verified[1].trim()) { + problems.push('Fill in **How it was verified**: the commands you ran and their results. UI changes need before/after screenshots.'); + } + + const { owner, repo } = context.repo; + const issue_number = pr.number; + const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number }); + const existing = comments.find(c => c.user.type === 'Bot' && c.body.includes(MARKER)); + const labeled = pr.labels.some(l => l.name === LABEL); + + if (problems.length) { + const text = `${MARKER}\nThanks for the PR. Before review, please update the description (see [CONTRIBUTING.md](https://github.com/${owner}/${repo}/blob/main/CONTRIBUTING.md)):\n\n${problems.map(p => `- ${p}`).join('\n')}\n\nThis check reruns when you edit the description.`; + if (existing) { + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: text }); + } else { + await github.rest.issues.createComment({ owner, repo, issue_number, body: text }); + } + if (!labeled) { + await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [LABEL] }); + } + return; + } + + // Only undo what this check did: the label is cleared only when our comment exists. + if (existing) { + await github.rest.issues.deleteComment({ owner, repo, comment_id: existing.id }); + if (labeled) { + await github.rest.issues.removeLabel({ owner, repo, issue_number, name: LABEL }).catch(() => {}); + } + } diff --git a/.gitignore b/.gitignore index a3a6a71d2..88c7d40bd 100644 --- a/.gitignore +++ b/.gitignore @@ -30,9 +30,13 @@ target/ test-results/ playwright-report/ -# Local AI agent files -CLAUDE.md -.claude/ +# Local AI agent files. Shared skills live in .agents/skills; .claude/skills links to them. +CLAUDE.local.md +.claude/* +!.claude/skills/ +.claude/skills/* +!.claude/skills/verify +!.claude/skills/issue-fix # Local reference screenshots pycharm-screenshots/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..35f9cdf52 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,118 @@ +# OpenIDEA — contributor guide + +Rust + Tauri 2 desktop IDE with full-featured, multi-language code intelligence. +Visual style: dark, low-contrast greys, one cool accent +(`src/styles/tokens.css` is the source of truth for colours and metrics). + +## Layout + +- `crates/ide-*` — backend crates; one domain each. Depend on `ide-core` for shared + types; never reach into another crate's internals. +- `src-tauri/` — glue only: `state.rs` (AppState / Project), `commands/.rs` + (thin typed IPC adapters), `events.rs`. No domain logic here. +- `src/bindings.ts` — generated by tauri-specta (`pnpm gen:bindings`). Never edit. +- `src/platform/` — ipc (`call`, `onIdeEvent`, `nextRequestId`), actions registry, + keymap + dispatcher, context keys / `when` clauses, project store. +- `src/workbench/contributions.ts` — extension points (tool windows, status items, + overlays, editor kinds) and cross-feature services (editor, tool windows, notifications). +- `src/ui/` — design-system primitives. `src/features//` — one feature each, + exporting `activate()` registered in `src/features/index.ts`. +- `languages/.toml` — language definitions (detection, comments, LSP, DAP). +- `crates/ide-host` — the `openidea-server` binary for remote development. +- `wit/` — plugin interface definitions; `plugins/` — sample and bundled plugins; + `templates/` — plugin starter templates. +- `e2e/` — Playwright UI tests (`pnpm e2e`). `fixtures/` — sample projects per language + for manual and automated testing. + +## Rules + +- IPC coordinates: 0-based lines, **UTF-16** columns/offsets everywhere (`ide-core::text`). +- Every command returns `IdeResult`; convert internal errors at the crate edge. +- Streaming → `tauri::ipc::Channel`; cancellable → UI-allocated `RequestId` + + `cancel_request`. Backend → UI notifications → `IdeEvent` via `EventSink`. +- Long work never blocks the UI thread or a Tauri command thread: use + `spawn_blocking`/rayon, honour `CancellationToken`. +- Frontend features never import each other; communicate via actions, contributions + and services. All user-invocable behaviour is an action with a stable id; default + shortcuts live in `src/platform/keymaps/`. +- Styling: CSS Modules + tokens only (no hex literals in components). Inline SVG + icons from `src/ui/icons.tsx`, never emoji. Real `