Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
237 changes: 237 additions & 0 deletions .claude/skills/rtk-triage/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
---
description: >
Triage complet RTK : exécute issue-triage + pr-triage en parallèle,
puis croise les données pour détecter doubles couvertures, trous sécurité,
P0 sans PR, et conflits internes. Sauvegarde dans claudedocs/RTK-YYYY-MM-DD.md.
Args: "en"/"fr" pour la langue (défaut: fr), "save" pour forcer la sauvegarde.
allowed-tools:
- Bash
- Write
- Read
- AskUserQuestion
---

# /rtk-triage

Orchestrateur de triage RTK. Fusionne issue-triage + pr-triage et produit une analyse croisée.

---

## Quand utiliser

- Hebdomadaire ou avant chaque sprint
- Quand le backlog PR/issues grossit rapidement
- Pour identifier les doublons avant de reviewer

---

## Workflow en 4 phases

### Phase 0 — Préconditions

```bash
git rev-parse --is-inside-work-tree
gh auth status
```

Vérifier que la date actuelle est connue (utiliser `date +%Y-%m-%d`).

---

### Phase 1 — Data gathering (parallèle)

Lancer les deux collectes simultanément :

**Issues** :
```bash
gh repo view --json nameWithOwner -q .nameWithOwner

gh issue list --state open --limit 150 \
--json number,title,author,createdAt,updatedAt,labels,assignees,body

gh issue list --state closed --limit 20 \
--json number,title,labels,closedAt

gh api "repos/{owner}/{repo}/collaborators" --jq '.[].login'
```

**PRs** :
```bash
gh pr list --state open --limit 60 \
--json number,title,author,createdAt,updatedAt,additions,deletions,changedFiles,isDraft,mergeable,reviewDecision,statusCheckRollup,body

# Pour chaque PR, récupérer les fichiers modifiés (nécessaire pour overlap detection)
# Prioriser les PRs candidates (même domaine, même auteur)
gh pr view {num} --json files --jq '[.files[].path] | join(",")'
```

---

### Phase 2 — Triage individuel

Exécuter les analyses de `/issue-triage` et `/pr-triage` séparément (même logique que les skills individuels) pour produire :

**Issues** :
- Catégorisation (Bug/Feature/Enhancement/Question/Duplicate)
- Risque (Rouge/Jaune/Vert)
- Staleness (>30j)
- Map `issue_number → [PR numbers]` via scan `fixes #N`, `closes #N`, `resolves #N`

**PRs** :
- Taille (XS/S/M/L/XL)
- CI status (clean/dirty)
- Nos PRs vs externes
- Overlaps (>50% fichiers communs entre 2 PRs)
- Clusters (auteur avec 3+ PRs)

Afficher les tableaux standards de chaque skill (voir SKILL.md de issue-triage et pr-triage pour le format exact).

---

### Phase 3 — Analyse croisée (cœur de ce skill)

C'est ici que ce skill apporte de la valeur au-delà des deux skills individuels.

#### 3.1 Double couverture — 2 PRs pour 1 issue

Pour chaque issue liée à ≥2 PRs (via scan des bodies + overlap fichiers) :

| Issue | PR1 (infos) | PR2 (infos) | Verdict recommandé |
|-------|-------------|-------------|-------------------|
| #N (titre) | PR#X — auteur, taille, CI | PR#Y — auteur, taille, CI | Garder la plus ciblée. Fermer/coordonner l'autre |

Règle de verdict :
- Préférer la plus petite (XS < S < M) si même scope
- Préférer CI clean sur CI dirty
- Préférer "nos PRs" si l'une est interne
- Si overlap de fichiers >80% → conflit quasi-certain, signaler

#### 3.2 Trous de couverture sécurité

Pour chaque issue rouge (#640-type security review) :
- Lister les sous-findings mentionnés dans le body
- Croiser avec les PRs existantes (mots-clés dans titre/body)
- Identifier les findings sans PR

Format :
```
## Issue #N — security review (finding par finding)
| Finding | PR associée | Status |
|---------|-------------|--------|
| Description finding 1 | PR#X | En review |
| **Description finding critique** | **AUCUNE** | ⚠️ Trou |
```

#### 3.3 P0/P1 bugs sans PR

Issues labelisées P0 ou P1 (ou mots-clés : "crash", "truncat", "cap", "hardcoded") sans aucune PR liée.

Format :
```
## Bugs critiques sans PR
| Issue | Titre | Pattern commun | Effort estimé |
|-------|-------|----------------|---------------|
```

Chercher un pattern commun (ex: "cap hardcodé", "exit code perdu") — si 3+ bugs partagent un pattern, suggérer un sprint groupé.

#### 3.4 Nos PRs dirty — causes probables

Pour chaque PR interne avec CI dirty ou CONFLICTING :
- Vérifier si un autre PR touche les mêmes fichiers
- Vérifier si un merge récent sur develop peut expliquer le conflit
- Recommander : rebase, fermeture, ou attente

Format :
```
## Nos PRs dirty
| PR | Issue(s) | Cause probable | Action |
|----|----------|----------------|--------|
```

#### 3.5 PRs sans issue trackée

PRs internes sans `fixes #N` dans le body — signaler pour traçabilité.

---

### Phase 4 — Output final

#### Afficher l'analyse croisée complète (sections 3.1 → 3.5)

Puis afficher le résumé chiffré :

```
## Résumé chiffré — YYYY-MM-DD

| Catégorie | Count |
|-----------|-------|
| PRs prêtes à merger (nos) | N |
| Quick wins externes | N |
| Double couverture (conflicts) | N paires |
| P0/P1 bugs sans PR | N |
| Security findings sans PR | N |
| Nos PRs dirty à rebaser | N |
| PRs à fermer (recommandé) | N |
```

#### Sauvegarder dans claudedocs

```bash
date +%Y-%m-%d # Pour construire le nom de fichier
```

Sauvegarder dans `claudedocs/RTK-YYYY-MM-DD.md` avec :
- Les tableaux de triage issues + PRs (Phase 2)
- L'analyse croisée complète (Phase 3)
- Le résumé chiffré

Confirmer : `Sauvegardé dans claudedocs/RTK-YYYY-MM-DD.md`

---

## Format du fichier sauvegardé

```markdown
# RTK Triage — YYYY-MM-DD

Croisement issues × PRs. {N} PRs ouvertes, {N} issues ouvertes.

---

## 1. Double couverture
...

## 2. Trous sécurité
...

## 3. P0/P1 sans PR
...

## 4. Nos PRs dirty
...

## 5. Nos PRs prêtes à merger
...

## 6. Quick wins externes
...

## 7. Actions prioritaires
(liste ordonnée par impact/urgence)

---

## Résumé chiffré
...
```

---

## Règles

- Langue : argument `en`/`fr`. Défaut : `fr`. Les commentaires GitHub restent toujours en anglais.
- Ne jamais poster de commentaires GitHub sans validation utilisateur (AskUserQuestion).
- Si >150 issues ou >60 PRs : prévenir l'utilisateur, proposer de filtrer par label ou date.
- L'analyse croisée (Phase 3) est toujours exécutée — c'est la valeur ajoutée de ce skill.
- Le fichier claudedocs est sauvegardé automatiquement sauf si l'utilisateur dit "no save".
13 changes: 13 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Summary
<!-- What does this PR do? Keep it short (1-3 bullet points). -->

-

## Test plan
<!-- How did you verify this works? -->

- [ ] `cargo fmt --all && cargo clippy --all-targets && cargo test`
- [ ] Manual testing: `rtk <command>` output inspected

> **Important:** All PRs must target the `develop` branch (not `master`).
> See [CONTRIBUTING.md](../blob/master/CONTRIBUTING.md) for details.
43 changes: 43 additions & 0 deletions .github/workflows/pr-target-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: PR Target Branch Check

on:
pull_request_target:
types: [opened, edited]

jobs:
check-target:
runs-on: ubuntu-latest
# Skip develop→master PRs (maintainer releases)
if: >-
github.event.pull_request.base.ref == 'master' &&
github.event.pull_request.head.ref != 'develop'
steps:
- name: Add wrong-base label
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;

// Add label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['wrong-base']
});

// Post comment
const body = `👋 Thanks for the PR! It looks like this targets \`master\`, but all PRs should target the **\`develop\`** branch.

Please update the base branch:
1. Click **Edit** at the top right of this PR
2. Change the base branch from \`master\` to \`develop\`

See [CONTRIBUTING.md](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/master/CONTRIBUTING.md) for details.`;

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: body
});
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ SHARED utils.rs Helpers N/A ✓
tee.rs Full output recovery N/A ✓
```

**Total: 60 modules** (38 command modules + 22 infrastructure modules)
**Total: 66 modules** (44 command modules + 22 infrastructure modules)

### Module Count Breakdown

Expand Down
41 changes: 41 additions & 0 deletions src/cargo_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ fn restore_double_dash_with_raw(args: &[String], raw_args: &[String]) -> Vec<Str
return args.to_vec();
}

// If args already contain `--` (Clap preserved it), no restoration needed
if args.iter().any(|a| a == "--") {
return args.to_vec();
}

// Find `--` in the original command line
let sep_pos = match raw_args.iter().position(|a| a == "--") {
Some(pos) => pos,
Expand Down Expand Up @@ -1054,6 +1059,42 @@ mod tests {
assert_eq!(result, vec!["--", "-D", "warnings"]);
}

#[test]
fn test_restore_double_dash_clippy_with_package_flags() {
// rtk cargo clippy -p my-service -p my-crate -- -D warnings
// Clap with trailing_var_arg preserves "--" when args precede it
// → clap gives ["-p", "my-service", "-p", "my-crate", "--", "-D", "warnings"]
let args: Vec<String> = vec![
"-p".into(),
"my-service".into(),
"-p".into(),
"my-crate".into(),
"--".into(),
"-D".into(),
"warnings".into(),
];
let raw = vec![
"rtk".into(),
"cargo".into(),
"clippy".into(),
"-p".into(),
"my-service".into(),
"-p".into(),
"my-crate".into(),
"--".into(),
"-D".into(),
"warnings".into(),
];
let result = restore_double_dash_with_raw(&args, &raw);
// Should NOT double the "--"
assert_eq!(
result,
vec!["-p", "my-service", "-p", "my-crate", "--", "-D", "warnings"]
);
// Verify only one "--" exists
assert_eq!(result.iter().filter(|a| *a == "--").count(), 1);
}

#[test]
fn test_filter_cargo_build_success() {
let output = r#" Compiling libc v0.2.153
Expand Down
1 change: 1 addition & 0 deletions src/cc_economics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::utils::{format_cpt, format_tokens, format_usd};

// ── Constants ──

#[allow(dead_code)]
const BILLION: f64 = 1e9;

// API pricing ratios (verified Feb 2026, consistent across Claude models <=200K context)
Expand Down
1 change: 1 addition & 0 deletions src/ccusage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ fn build_command() -> Option<Command> {
}

/// Check if ccusage CLI is available (binary or via npx)
#[allow(dead_code)]
pub fn is_available() -> bool {
build_command().is_some()
}
Expand Down
Loading