Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .cursor/rules/deepseek.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,15 @@ Official first call: https://api-docs.deepseek.com/
| ----- | ---- | ----- |
| Upstream 401 after alias maps to DeepSeek | Only Moonshot key saved | Require `deepseekKeyEncrypted` when resolved provider is `deepseek` (doctor / set-key) |

<!-- last-verified: 2026-07 -->
### `Downgraded flash turns are nearly free via prompt cache`

In a multi-turn agent flow, the conversation context grows monotonically, so
every successive tool-result request re-sends nearly the whole prior context.
DeepSeek auto-caches this prefix, making iterated cheap turns extraordinarily
cheap.

| Symptom | Cause | Fix |
| ----- | ---- | ----- |
| `deepseek-v4-flash` tool-result turns log `est_usd ≈ 0.000` with ~99% `cache_hit_tokens` (e.g. 40,576 hit / 46 miss) while `tokens_in` climbs each turn | DeepSeek prompt caching keys on a matching prefix; because each turn appends one tool result to unchanged prior messages, the prefix (and thus the cache) is reused | Expected and desirable — do not treat near-zero flash-cost turns in a stable conversation as under-counting. It means the per-turn downgrade is already paying for itself; run medium write-turns on `pro` and small/read-only on `flash` without fear of cost blow-up |

`<!-- last-verified: 2026-08 -->`
30 changes: 28 additions & 2 deletions .cursor/rules/gateway.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,35 @@ when the model has a mapped cheaper variant:
| Symptom | Cause | Fix |
| ------- | ----- | --- |
| "fix linter errors in some files" stays on the expensive model instead of flash | `isStrongEditing` matches "fix" and blocks the `automation` downgrade | Let lint tasks classify as automation even with a strong-editing word present (`isLintTask` → bypass the `!isStrongEditing` guard) |
| `glm-5.2` / `kimi-k3` never downgrade on automation tasks | Model has no mapped cheaper variant and falls back to `deepseek-v4-flash` (`defaultSubagentModel`). Override also changes provider (re-resolved via `ResolveModel`), so cross-provider routing works: e.g. `glm-5.2` → `deepseek-v4-flash` routes to `provider=deepseek`, not Z.AI. To add a same-provider cheap variant (e.g. `glm-4.7` as flash for `glm-5.2`), uncomment the entry in `modelOverrideMap` — it takes priority over the universal fallback. |
| `glm-5.2` / `kimi-k3` never downgrade on automation tasks | Model has no mapped cheaper variant and falls back to `deepseek-v4-flash` (`defaultFlashModel`). Override also changes provider (re-resolved via `ResolveModel`), so cross-provider routing works: e.g. `glm-5.2` → `deepseek-v4-flash` routes to `provider=deepseek`, not Z.AI. To add a same-provider cheap variant (e.g. `glm-4.7` as flash for `glm-5.2`), uncomment the entry in `modelOverrideMap` — it takes priority over the universal fallback. |

<!-- last-verified: 2026-08 -->
#### Per-turn routing tiers (`flash` / `pro` / `keep`)

Within one multi-step agent flow, each tool-result request is routed by an
`OverrideTier` decided from the **tool result size + the tool name** (not the
content class). Real 71s GLM 5.2 refactor session: 7/10 turns went `flash`,
2 decision-heavy `pro`, initial prompt + one large result stayed `glm-5.2`.

| Signal | Tier | Model | Rationale |
| ------ | ---- | ----- | --------- |
| tool result, size `small` | flash | `deepseek-v4-flash` | short output → cheap interpretation |
| tool result, size `medium`, read-only tool (`Read`/`Grep`/`Glob`/search) | flash | `deepseek-v4-flash` | just reading → no pro needed |
| tool result, size `medium`, write/decision tool (`Shell`/`StrReplace`/tests/unknown) | pro | `deepseek-v4-pro` | may decide state changes → needs a reasoning model |
| tool result, size `large` | keep | original | big output needs pro interpretation |
| user prompt | keep | original | content classifier decides (see above) |

Route on the resolved `tool_name` (matched from the last `tool_call_id` back to
the assistant `tool_calls[].function.name`). Treat any non-read-only tool name —
**including empty/unknown** — as write/decision → pro. A cheap tier is only safe
when the result can be interpreted without deep reasoning.

#### Flash verbosity cap (output token ceiling on cheap turns)

| Symptom | Cause | Fix |
| ------- | ----- | --- |
| Agent flow "takes too long / overthought a lot" for a simple change | On `flash` turn the model is over-confident and emits 4–16k output tokens (~20–106s) rather than a terse next step | Cap cheap tiers' `max_tokens` to `toolVerbosityCap` (1500) so flash turns stay terse. Apply the cap even when the model override is a no-op (flow already on flash). Cheap turns should land at 78–600 output tokens, not thousands |

`<!-- last-verified: 2026-08 -->`

### `DiagnosticDump: gated message-array dumps for content-extraction analysis`

Expand Down
4 changes: 2 additions & 2 deletions .cursor/rules/release.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ alwaysApply: false
## Release Process

- Version is tracked in `VERSION`.
- `make release-patch` / `make release-minor` / `make release-major` bumps the
version in `VERSION`, commits, tags, and pushes.
- `make release-patch` / `make release-minor` bumps the version in `VERSION`,
commits, tags, and pushes.
- **Tag push triggers CI:** the `.github/workflows/release-binaries.yaml`
workflow runs on push to `main`, pull requests to `main`, and pushes of tags
matching `v*`.
Expand Down
159 changes: 159 additions & 0 deletions .cursor/skills/open-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
---
name: open-pr
description: >
Create a GitHub PR from the current branch in this Discursive Go repo.
Generates PR description with functional line count, key files to review,
and non-technical summary from the git diff. Supports draft mode.
disable-model-invocation: true
allowed-tools: Bash, Read
---

# /open-pr — Create a GitHub Pull Request

## Call budget

This skill MUST complete in **≤8 total tool calls**:
- Step 0: 1 Bash — batch state gathering
- Step 1: 1 AskQuestion
- Step 2: 1 Bash — dump diff + stats
- Step 3: 1 Read — read the dumped diff once (100ms, tiny)
- Step 4: 2 Bash — `git push` then `gh pr create` (dependent; can't chain a
heredoc body onto a `&&` push safely)
- Step 5: 1 Bash — cleanup `tmp/diff.diff`
- Step 6: inline report

Every call beyond 8 (and any Bash call that isn't one of the above) is a bug.
The prior session made ~15 redundant Shell calls (multiple `git status`/`git
log`/`git branch`, per-file `git diff`, `git fetch`, `gh pr view`). None of
those are allowed in this flow.

## Flow (strict)

### Step 0: gather state (1 batch Bash call)

With `working_directory` set to the repo root:

```bash
git branch -vv && git branch -r && git log -15 --oneline
```

From this output, identify:
- Current branch name
- Remote base branch options (pick `main`, plus any other active branches)
- Recent commits (the branch's story)

**Do NOT run `git status`, `git log`, or `git branch` separately.**

### Step 1: ask base branch (AskQuestion)

**Always** present a choice of base branches derived from step 0. Include `main`
and any other visible active branches plus an "Other" option. Never default.

### Step 2: dump functional diff + stats (1 Bash call)

Dump to gitignored `./tmp/diff.diff`, plus `--shortstat` and `--stat`:

```bash
git diff <BASE>...HEAD -- ':(exclude)*.md' ':(exclude)*.mdc' ':(exclude)**/test/**' ':(exclude)**/*_test.go' ':(exclude)**/*.txt' ':(exclude).cursor/rules/**' ':(exclude).cursor/skills/**' ':(exclude)examples/**' ':(exclude)VERSION' > ./tmp/diff.diff && echo "---SHORTSTAT---" && git diff <BASE>...HEAD --shortstat -- ':(exclude)*.md' ':(exclude)*.mdc' ':(exclude)**/test/**' ':(exclude)**/*_test.go' ':(exclude)**/*.txt' ':(exclude).cursor/rules/**' ':(exclude).cursor/skills/**' ':(exclude)examples/**' ':(exclude)VERSION' && echo "---STAT---" && git diff <BASE>...HEAD --stat -- ':(exclude)*.md' ':(exclude)*.mdc' ':(exclude)**/test/**' ':(exclude)**/*_test.go' ':(exclude)**/*.txt' ':(exclude).cursor/rules/**' ':(exclude).cursor/skills/**' ':(exclude)examples/**' ':(exclude)VERSION' && echo "---NONFUNCSTAT---" && git diff <BASE>...HEAD --shortstat -- '*.md' '*.mdc' '.cursor/skills/**' '*.txt'
```

If the functional diff is empty (zero lines, or only binary `usage.db` type
junk), fall back to the full diff (no pathspec) and treat as a rules/docs-only
PR per that section below.

### Step 3: read the diff ONCE (1 Read call)

Read `./tmp/diff.diff` with the Read tool. Derive from it:

- **Summary bullets (4–6):** what changed and why. Each 1–2 lines. Do not
enumerate markdown/rules/test file changes here.
- **Key files to review:** pick 3–8 from the `--stat` in step 2 (largest first).

Prefer this single Read over many per-file `git diff` calls.

### Step 4: push then create PR (2 Bash calls)

Push the branch:

```bash
git push -u origin HEAD
```

If the branch is already pushed (no-op), that's fine — `gh pr create` still
works. If push fails for a non-obvious reason (not "Everything up-to-date"),
surface the error and stop.

Then create the PR (body assembled from step 3's read):

```bash
gh pr create --base <BASE> --title "<short imperative description>" --body "$(cat <<'EOF'
**Functional lines changed:** <N> files, +<I> −<D>

## Key files to review
- `…` — …
- `…` — …

## Summary
- **…**: …
- **…**: …

## Non-functional changes # only if substantial rules/skills/README changes
- `…` — …
EOF
)"
```

For draft PRs, add `--draft`. Capture the PR URL from the output. The
`## Non-functional changes` section is included only when step 2's
`---NONFUNCSTAT---` shows substantial changed files (e.g. README + rules large
enough to matter to reviewers). Keep it brief.

### Step 5: cleanup (1 Bash call)

```bash
rm -f ./tmp/diff.diff
```

### Step 6: report (inline, no call)

Report the PR URL captured from step 4's `gh pr create` output. Done. Do not
`gh pr view` after creation unless the PR appeared to fail.

## PR body order (always)

1. `**Functional lines changed:** …`
2. `## Key files to review`
3. Optional `## TODO_IN_THIS_PR`
4. `## Summary`
5. Optional `## Non-functional changes`

## PR title format

```
<short imperative description>
```

No JIRA key, no `[WIP]` prefix. For draft PRs, use `--draft`.

Examples:
- `Add smart router with content-based model downgrade`
- `Fix usage query time-window handling`

## Rules-only / docs-only PRs (zero functional files)

When the functional diff is empty:

- `**Functional lines changed:**` → `0` explicitly.
- `## Key files to review` → 3–8 paths from the **full** diff (largest first).
- `## Summary` → describe the actual changed files.

## Anti-patterns (do NOT do these)

- Do NOT read individual file diffs with `git diff -- <file>` — use the single
temp-file dump instead.
- Do NOT run `git status` — the branch state is from step 0.
- Do NOT run `git fetch` as a separate call — if needed, prepend to step 0.
- Do NOT run `gh pr view` after creation unless step 4 output is suspicious.
- Do NOT run more than one Bash call for independent commands — chain with `&&`.
- Do NOT forget `working_directory` — every Bash call that touches the repo
needs it set explicitly.
Loading