Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
86a8246
docs(issue-1178): record maintainer feedback before implementation
josecelano May 12, 2026
5b0a786
feat(tracker-client): add tracker_checker monitor udp command
josecelano May 12, 2026
2e38367
docs(issue-1178): add manual verification against demo tracker
josecelano May 12, 2026
3067868
docs(issue-1178): add manual verification against old (down) demo tra…
josecelano May 12, 2026
4d1493c
docs(issue-1178): add post-implementation refactor plan
josecelano May 12, 2026
47d7092
docs(planning): add refactor plan template and create-refactor-plan s…
josecelano May 12, 2026
7fb474b
docs(planning): reorganise refactor-plans into drafts/open/closed sub…
josecelano May 12, 2026
d786a92
docs(planning): move agent-docs-refactor-plan to closed
josecelano May 12, 2026
df8c380
docs(issue-1178): fix spec accuracy and add all-null latency unit test
josecelano May 12, 2026
7dac161
docs(tracker-client): document timeout-only nature of monitor_udp int…
josecelano May 12, 2026
664a125
docs(issue-1178): correct Task 6 file reference to app.rs
josecelano May 12, 2026
db747f6
docs(issue-1178): document last_ms null on timeout in AC3 and tick al…
josecelano May 12, 2026
7d404ba
docs(tracker-client): clarify intent of both duration-check guards in…
josecelano May 12, 2026
36f5511
docs(tracker-client): document u64::MAX fallback is unreachable in el…
josecelano May 12, 2026
1a650bf
docs(tracker-client): document timeout_percent denominator includes e…
josecelano May 12, 2026
617b1f5
fix(tracker-client): measure elapsed_ms after DNS resolution
josecelano May 12, 2026
dbd452b
refactor(tracker-client): extract run_probe_loop from run_monitor
josecelano May 12, 2026
ec34d60
refactor(tracker-client): add MonitorStats conversion from Stats
josecelano May 12, 2026
c5b0049
docs(planning): defer item 14 and close monitor udp refactor plan
josecelano May 12, 2026
aece34b
refactor(tracker-client): split tracker_checker integration tests by …
josecelano May 12, 2026
f76c733
test(tracker-client): address copilot review feedback
josecelano May 12, 2026
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
174 changes: 174 additions & 0 deletions .github/skills/dev/planning/create-refactor-plan/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
---
name: create-refactor-plan
description: Guide for creating refactor plans in the torrust-tracker project. Covers identifying quality gaps, decomposing them into trackable items ordered by impact vs effort, writing the plan document, and committing it. Use when planning improvements to readability, testability, maintainability, modularity, or documentation quality. Triggers on "create refactor plan", "refactor plan", "plan refactor", "post-implementation improvements", "code quality plan", or "technical debt plan".
metadata:
author: torrust
version: "1.0"
semantic-links:
related-artifacts:
- docs/templates/REFACTOR-PLAN.md
---

# Creating Refactor Plans

## When to Write a Refactor Plan

Write a refactor plan when:

- A completed implementation has known quality gaps that are not blocking but worth tracking.
- A code review, post-implementation audit, or routine quality check identifies improvements
across multiple dimensions (readability, testability, maintainability, modularity, docs).
- The improvements are too numerous or varied to address in a single commit but collectively
deserve a structured approach.

Do **not** write a refactor plan for:

- A single trivial fix — just fix it in place.
- Bug fixes — those belong in issue specs (`docs/templates/ISSUE.md`).
- Architectural decisions — those belong in ADRs (`docs/templates/ADR.md`).

## Workflow Overview

1. **Identify quality gaps** by auditing the code, spec, and tests.
2. **Decompose** gaps into discrete, independently completable items.
3. **Order** items by impact vs effort (highest impact / lowest effort first).
4. **Draft the plan** using the template.
5. **Run linters** and fix any issues.
6. **Commit** the plan.
7. **Implement** items one at a time, ticking checkboxes as each is done.
8. **Revisit** the plan after implementation to evaluate whether the template and skill
need improvements.

## Step-by-Step Process

### Step 1: Identify and Categorize Quality Gaps

Review the following dimensions systematically:

| Dimension | Questions to Ask |
| --------------- | ----------------------------------------------------------------------------------------------------- |
| Correctness | Are there edge cases not tested? Does documentation match actual behaviour? |
| Readability | Is intent clear at a glance? Are names self-explanatory? Are surprising choices explained? |
| Testability | Can behaviour be verified without spawning a process? Are unit and integration paths both covered? |
| Maintainability | Are concerns separated? Is any function too long or doing too many things? |
| Modularity | Are abstractions reusable? Are conversions done in idiomatic places (e.g. `From` impls)? |
| Documentation | Are public APIs documented? Are non-obvious invariants or contract details captured in spec and code? |

### Step 2: Write Each Item

Each item in the plan must contain:

- **Problem**: what is wrong and why it matters — be specific (name files, functions, line ranges).
- **Files**: the files affected.
- **Change**: what exactly changes — prefer concrete before/after examples over vague descriptions.

Use the effort and impact labels consistently:

| Impact | Meaning |
| ------ | --------------------------------------------------------- |
| High | Correctness, observability, or user-facing contract issue |
| Medium | Developer experience, maintainability, clarity |
| Low | Nice-to-have polish or future-proofing |

| Effort | Meaning |
| ------- | --------------------------------------------------- |
| Trivial | One-liner or wording change, no logic involved |
| Low | Small, self-contained code or doc change (< 1 hour) |
| Medium | Moderate refactor or new abstraction (1–4 hours) |
| High | Significant new code, e.g. mock server (> 4 hours) |

### Step 3: Order Items

Sort items in the plan and in the execution table by:

1. Highest impact first.
2. Lowest effort first within the same impact band.

This ensures the most valuable, cheapest improvements are visible and tackled first.

### Step 4: Create the Plan File

Plans follow the same `drafts/` → `open/` → `closed/` lifecycle as issue specs.

```bash
touch docs/refactor-plans/drafts/{short-description}.md
```

Use the template at [docs/templates/REFACTOR-PLAN.md](../../../../docs/templates/REFACTOR-PLAN.md).

Naming convention: `{related-artifact-short-description}.md`

Example: `1178-monitor-udp-post-implementation-improvements.md`

Each item heading uses a checkbox and an impact/effort label:

```markdown
### 1. [ ] {Title} [HIGH impact / TRIVIAL effort]
```

The execution table also has a `Status` column with `[ ]`:

```markdown
| 1 | [ ] | {Item} | High | Trivial |
```

To mark an item done, flip `[ ]` → `[x]` in **both** the heading and the table row.

### Step 5: Validate and Commit

Move the plan from `drafts/` to `open/` when implementation starts:

```bash
git mv docs/refactor-plans/drafts/{filename}.md docs/refactor-plans/open/{filename}.md
```

```bash
linter all # Must pass

git add docs/refactor-plans/
git commit -S -m "docs({scope}): add refactor plan for {description}"
```

### Step 6: Implement and Track Progress

Work through items in order. After completing each item:

1. Flip `[ ]` → `[x]` in the item heading.
2. Flip `[ ]` → `[x]` in the execution table row.
3. Run `linter all` and fix any new issues.
4. Commit the implementation and the updated plan together.

When all items are done, move the plan to `closed/`:

```bash
git mv docs/refactor-plans/open/{filename}.md docs/refactor-plans/closed/{filename}.md
git commit -S -m "docs({scope}): close refactor plan for {description}"
```

### Step 7: Revisit the Template and Skill

After implementing all items, evaluate:

- Did the template structure make items easy to write and track?
- Were the impact/effort labels consistently interpreted?
- Is anything missing that would have made the plan more useful?

Update `docs/templates/REFACTOR-PLAN.md` and this skill file if improvements are identified.

## Naming Convention

File name format: `{related-artifact-short-description}.md`

| Lifecycle stage | Folder |
| --------------- | ----------------------------- |
| Being written | `docs/refactor-plans/drafts/` |
| In progress | `docs/refactor-plans/open/` |
| All done | `docs/refactor-plans/closed/` |

## Relationship to Other Artifacts

| Artifact | When to Use Instead |
| ------------- | ----------------------------------------------------------------- |
| Issue spec | When the improvement is a bug fix or new feature |
| ADR | When the improvement requires documenting an architectural choice |
| Refactor plan | When improvements are quality gaps with no new functionality |
104 changes: 100 additions & 4 deletions console/tracker-client/src/console/clients/checker/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,28 @@
//! }
//! ```
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use clap::Parser;
use bittorrent_primitives::info_hash::InfoHash as TorrustInfoHash;
use clap::{Parser, Subcommand};
use tracing::level_filters::LevelFilter;
use url::Url;

use super::config::Configuration;
use super::console::Console;
use super::error::{AppError, ConfigSource};
use super::service::{CheckResult, Service};
use super::monitor::udp::{run_monitor, MonitorUdpConfig, DEFAULT_INFO_HASH};
use super::service::Service;
use crate::console::clients::checker::config::parse_from_json;

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
#[command(subcommand)]
command: Option<Command>,

/// Path to the JSON configuration file.
#[clap(short, long, env = "TORRUST_CHECKER_CONFIG_PATH")]
config_path: Option<PathBuf>,
Expand All @@ -80,15 +88,54 @@ struct Args {
config_content: Option<String>,
}

#[derive(Subcommand, Debug)]
enum Command {
/// Run periodic monitor checks.
Monitor {
#[command(subcommand)]
protocol: MonitorProtocol,
},
}

#[derive(Subcommand, Debug)]
enum MonitorProtocol {
/// Monitor a UDP tracker using announce probes.
Udp {
/// UDP tracker URL.
#[arg(long, value_parser = parse_udp_url)]
url: Url,

/// Seconds between probes.
#[arg(long, default_value_t = 300, value_parser = clap::value_parser!(u64).range(1..))]
interval: u64,

/// Probe timeout in seconds.
#[arg(long, default_value_t = 10, value_parser = clap::value_parser!(u64).range(1..))]
timeout: u64,

/// Total monitor runtime in seconds.
#[arg(long, default_value_t = 86_400, value_parser = clap::value_parser!(u64).range(1..))]
duration: u64,

/// Info-hash used in announce requests.
#[arg(long, default_value = DEFAULT_INFO_HASH, value_parser = parse_info_hash)]
info_hash: TorrustInfoHash,
},
}

/// # Errors
///
/// Will return an `AppError::InvalidConfig` if the configuration cannot be parsed,
/// or an `AppError::Runtime` if the checks fail to execute.
pub async fn run() -> Result<Vec<CheckResult>, AppError> {
pub async fn run() -> Result<(), AppError> {
tracing_stdout_init(LevelFilter::INFO);

let args = Args::parse();

if let Some(command) = args.command {
return run_command(command).await;
}

let config = setup_config(args)?;

let console_printer = Console {};
Expand All @@ -98,7 +145,11 @@ pub async fn run() -> Result<Vec<CheckResult>, AppError> {
console: console_printer,
};

service.run_checks().await.map_err(|e| AppError::Runtime(e.to_string()))
service
.run_checks()
.await
.map_err(|e| AppError::Runtime(e.to_string()))
.map(|_results| ())
}

fn tracing_stdout_init(filter: LevelFilter) {
Expand Down Expand Up @@ -131,3 +182,48 @@ fn load_config_from_file(path: &PathBuf) -> Result<Configuration, AppError> {
message: e.to_string(),
})
}

async fn run_command(command: Command) -> Result<(), AppError> {
match command {
Command::Monitor {
protocol:
MonitorProtocol::Udp {
url,
interval,
timeout,
duration,
info_hash,
},
} => {
let config = MonitorUdpConfig {
url,
interval: Duration::from_secs(interval),
timeout: Duration::from_secs(timeout),
duration: Duration::from_secs(duration),
info_hash,
};

run_monitor(config)
.await
.map_err(|e| AppError::Runtime(format!("udp monitor failed: {e}")))
}
}
}

fn parse_udp_url(url_str: &str) -> Result<Url, String> {
let url = Url::parse(url_str).map_err(|e| format!("invalid URL: {e}"))?;

if url.scheme() != "udp" {
return Err("URL scheme must be udp".to_string());
}

if url.port().is_none() {
return Err("URL must include an explicit port".to_string());
}

Ok(url)
}

fn parse_info_hash(info_hash_str: &str) -> Result<TorrustInfoHash, String> {
TorrustInfoHash::from_str(info_hash_str).map_err(|e| format!("failed to parse info-hash `{info_hash_str}`: {e:?}"))
}
1 change: 1 addition & 0 deletions console/tracker-client/src/console/clients/checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ pub mod config;
pub mod console;
pub mod error;
pub mod logger;
pub mod monitor;
pub mod printer;
pub mod service;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod udp;
Loading
Loading