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
4 changes: 2 additions & 2 deletions .claude/skills/running-tend/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ ask is `wt -vv <command>`**. Re-running the failing command with `-vv`
writes a diagnostic bundle — a single report containing wt/git/OS versions,
shell integration, `wt config show`, `git worktree list --porcelain`, and a
`trace.log` of every git invocation with its output. The `-vv` output prints
the bundle's exact absolute path (`Logs, performance profile, and
diagnostics saved @ …`) followed by a ready-to-run `gh gist create --web
the bundle's exact absolute path (`Diagnostics and performance profile
saved @ …`) followed by a ready-to-run `gh gist create --web
<path>` line — **point the user at those printed lines; don't hand them a
hardcoded path**. In particular, never tell them to `cat
.git/wt/logs/diagnostic.md`: inside a linked worktree `.git` is a gitdir
Expand Down
38 changes: 10 additions & 28 deletions src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ use worktrunk::config::ConfigFileKind;
use worktrunk::git::Repository;
use worktrunk::path::format_path_for_display;
use worktrunk::shell_exec::Cmd;
use worktrunk::styling::{
eprintln, format_with_gutter, hint_message, info_message, warning_message,
};
use worktrunk::styling::{eprintln, hint_message, info_message, warning_message};

use crate::cli::version_str;
use crate::output;
Expand Down Expand Up @@ -349,11 +347,12 @@ pub(crate) fn write_if_verbose(verbose: u8, command_line: &str, error_msg: Optio
None => "Command completed successfully".to_string(),
};

// Collect and write the diagnostic bundle. It leads with the performance
// profile and inlines a (truncated) `trace.log`, so `diagnostic.md` is the
// human-facing doc — the headline names what it captured. The raw
// companions it doesn't carry in full (`trace.jsonl` machine source,
// `subprocess.log` uncapped bodies) are listed beneath it.
// Collect and write the diagnostic bundle. `diagnostic.md` is the single
// human-facing doc — it's what the headline names. The raw companions
// (`trace.jsonl`, `subprocess.log`) live alongside it in the same
// directory (named at the start of every `-vv` run), and the report body
// points at them directly (subprocess_log_path in the template above;
// the performance profile section names `trace.jsonl` as its source).
let report = DiagnosticReport::collect(&repo, command_line, context);

match report.write_diagnostic_file(&repo) {
Expand All @@ -362,34 +361,17 @@ pub(crate) fn write_if_verbose(verbose: u8, command_line: &str, error_msg: Optio
eprintln!(
"{}",
info_message(format!(
"Logs, performance profile, and diagnostics saved @ {path_display}"
"Diagnostics and performance profile saved @ {path_display}"
))
);

// The raw companions diagnostic.md doesn't carry in full, when their
// sinks opened; `trace.log` and the profile are omitted because the
// bundle already inlines them.
let companions: Vec<String> = [
crate::log_files::TRACE_JSONL.path(),
crate::log_files::SUBPROCESS.path(),
]
.into_iter()
.flatten()
.map(|p| format_path_for_display(&p))
.collect();
if !companions.is_empty() {
eprintln!("{}", format_with_gutter(&companions.join("\n"), None));
}

// Only show gh command if gh is installed
if is_gh_installed() {
let path_str = format_path_for_display(&path);
// URL with prefilled body: ## Gist\n\n[Paste URL]\n\n## Description\n\n[Describe the issue]
let issue_url = "https://github.com/max-sixty/worktrunk/issues/new?body=%23%23%20Gist%0A%0A%5BPaste%20gist%20URL%5D%0A%0A%23%23%20Description%0A%0A%5BDescribe%20the%20issue%5D";
let issue_url = "https://github.com/max-sixty/worktrunk/issues/new";
eprintln!(
"{}",
hint_message(cformat!(
"To report a bug, create a secret gist with <underline>gh gist create --web {path_str}</> and reference it from an issue at <underline>{issue_url}</>"
"To report a bug, open an issue (<underline>{issue_url}</>) and attach a secret gist: <underline>gh gist create --web {path_display}</>"
))
);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/integration_tests/config_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3134,7 +3134,7 @@ fn test_format_rejected_on_write_action_writes_verbose_diagnostic(repo: TestRepo
"stderr should include the clap conflict: {stderr}"
);
assert!(
stderr.contains("performance profile, and diagnostics saved"),
stderr.contains("Diagnostics and performance profile saved"),
"stderr should mention the verbose diagnostic: {stderr}"
);

Expand Down
41 changes: 15 additions & 26 deletions tests/integration_tests/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//! - `test_diagnostic_contains_required_sections`: All sections present
//! - `test_diagnostic_context_has_no_ansi_codes`: ANSI stripped for GitHub
//! - `test_diagnostic_trace_log_contains_git_commands`: Log has useful data
//! - `test_diagnostic_saved_message_with_vv`: -vv announces the saved report and its raw companions
//! - `test_diagnostic_saved_message_with_vv`: -vv announces the saved report
//! - `test_diagnostic_leads_with_profile`: Profile is the bundle's first section
//! - `test_diagnostic_written_to_correct_location`: File in .git/wt/logs/
//! - `test_diagnostic_gh_hint_with_vv`: Hint shows gist and issue URL when gh installed
Expand Down Expand Up @@ -90,7 +90,7 @@ fn test_diagnostic_report_file_format(mut repo: TestRepo) {
// Verify the stderr mentions the diagnostic was saved
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("performance profile, and diagnostics saved"),
stderr.contains("Diagnostics and performance profile saved"),
"Output should mention diagnostic was saved. stderr: {}",
stderr
);
Expand Down Expand Up @@ -312,10 +312,11 @@ fn test_diagnostic_trace_log_contains_git_commands(mut repo: TestRepo) {
);
}

/// The `-vv` end block names what the run captured (headline mentions the
/// performance profile) and lists the raw companions `diagnostic.md` doesn't
/// inline — `trace.jsonl` and `subprocess.log` — while omitting the files it
/// does carry (`trace.log`, and there's no `profile.txt`).
/// The `-vv` end block names what the run captured and saved
/// (`diagnostic.md`, the single human-facing doc). It doesn't re-list the
/// raw companion logs (`trace.jsonl`, `subprocess.log`) — the start-of-run
/// pointer (`Verbose logging to <dir>/`) already names the log directory,
/// and the report body points at the companions directly.
#[rstest]
fn test_diagnostic_saved_message_with_vv(mut repo: TestRepo) {
repo.add_worktree("feature");
Expand All @@ -325,20 +326,15 @@ fn test_diagnostic_saved_message_with_vv(mut repo: TestRepo) {

let stderr = String::from_utf8_lossy(&output.stderr);

// Headline names the profile and the diagnostic bundle.
// Headline names the diagnostics and performance profile.
assert!(
stderr.contains("performance profile, and diagnostics saved"),
stderr.contains("Diagnostics and performance profile saved"),
"end block should name what was captured. stderr: {stderr}"
);
// The non-duplicative raw companions are pointed at.
// The raw companion logs are not re-listed in the end block.
assert!(
stderr.contains("wt/logs/trace.jsonl") && stderr.contains("wt/logs/subprocess.log"),
"end block should list the raw companions diagnostic.md doesn't inline: {stderr}"
);
// Files whose content the bundle already carries are not re-listed.
assert!(
!stderr.contains("wt/logs/trace.log") && !stderr.contains("profile.txt"),
"end block should not list files inlined in the bundle: {stderr}"
!stderr.contains("wt/logs/trace.jsonl") && !stderr.contains("wt/logs/subprocess.log"),
"end block should not re-list the raw companion logs: {stderr}"
);
}

Expand Down Expand Up @@ -428,7 +424,7 @@ fn test_diagnostic_leads_with_profile(repo: TestRepo) {
"the profile is folded into diagnostic.md, not announced separately: {stderr}"
);
assert!(
stderr.contains("performance profile, and diagnostics saved"),
stderr.contains("Diagnostics and performance profile saved"),
"stderr should announce the diagnostic bundle. stderr: {stderr}"
);
}
Expand Down Expand Up @@ -839,7 +835,7 @@ fn test_vv_writes_diagnostic_on_success(repo: TestRepo) {
// stderr should mention diagnostic was saved
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("performance profile, and diagnostics saved"),
stderr.contains("Diagnostics and performance profile saved"),
"stderr should mention diagnostic was saved. stderr: {}",
stderr
);
Expand Down Expand Up @@ -868,7 +864,7 @@ fn test_vv_writes_diagnostic_on_error(mut repo: TestRepo) {
// stderr should mention diagnostic was saved
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("performance profile, and diagnostics saved"),
stderr.contains("Diagnostics and performance profile saved"),
"stderr should mention diagnostic was saved. stderr: {}",
stderr
);
Expand Down Expand Up @@ -901,13 +897,6 @@ fn test_vv_pointer_handles_split_init(repo: TestRepo) {
logs_dir.join("trace.log").exists(),
"trace.log should still be created"
);
// The end-of-run companion list surfaces the surviving raw file and drops
// subprocess.log, whose sink failed to open (`SUBPROCESS.path()` → None,
// filtered out by the `.flatten()` in `write_if_verbose`).
assert!(
stderr.contains("wt/logs/trace.jsonl") && !stderr.contains("wt/logs/subprocess.log"),
"end block should list the surviving companion and omit the failed subprocess.log: {stderr}"
);
}

/// With just -v, info-level logging goes to stderr but no log files are written.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
source: tests/integration_tests/diagnostic.rs
expression: normalized
---
↳ To report a bug, create a secret gist with gh gist create --web [DIAGNOSTIC_PATH] and reference it from an issue at https://github.com/max-sixty/worktrunk/issues/new?body=%23%23%20Gist%0A%0A%5BPaste%20gist%20URL%5D%0A%0A%23%23%20Description%0A%0A%5BDescribe%20the%20issue%5D
↳ To report a bug, open an issue (https://github.com/max-sixty/worktrunk/issues/new) and attach a secret gist: gh gist create --web [DIAGNOSTIC_PATH]
Loading