1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! Warn at launch when a checkout carries another harness's hook wiring.
//!
//! Why: `tm doctor`'s `hooks_foreign_conflict` probe already detects a
//! project whose `.claude/settings*.json` wires claude-mpm's hooks, and those
//! hooks fire inside a tm session. Running only under `tm doctor` means the
//! finding surfaces when someone thinks to look, which is not when it matters —
//! an operator learns their session is running two harnesses' hooks at the
//! moment the session starts, or not in time to act on it. This emits the same
//! finding on the launch path.
//!
//! What: [`warn_on_foreign_harness_config`] reads the same settings files the
//! doctor probe reads, through the same
//! [`crate::daemon::doctor::doctor_hooks_hygiene::candidate_settings_files`] and
//! [`crate::daemon::doctor::doctor_hooks_hygiene::read_settings`] entry points,
//! and applies the same
//! [`crate::core::standalone::hooks::cleanup::foreign_hook_event_names`]
//! predicate. One detector, two surfaces.
//!
//! **Report-only, permanently.** tm never removes another harness's hooks —
//! that call belongs to the operator (`core::doctor_repair` records the same
//! rule for the doctor surface). This module logs and returns; it has no
//! mutating path and must not grow one.
//!
//! Test: `foreign_harness_tests.rs`.
use ;
use warn;
use crateforeign_hook_event_names;
use crate;
/// Settings files under `dirs` that wire a foreign (claude-mpm) hook.
///
/// Why: split from the logging so the detection has a return value a test can
/// assert on, rather than a test having to capture a tracing subscriber.
/// What: for every existing `<dir>/.claude/settings{,.local}.json`, parses the
/// JSON through the doctor probe's own `read_settings` and keeps the path when
/// [`foreign_hook_event_names`] finds at least one foreign hook group. A file
/// that is missing, unreadable, or not a JSON object contributes nothing — an
/// unparseable settings file is reported by the doctor probe, which owns that
/// finding; this launch-path warning is about foreign hooks it can actually see.
/// Test: `detects_foreign_hooks_in_launch_dir`, `clean_dir_reports_nothing`,
/// `malformed_settings_file_is_skipped_not_panicked`.
pub
/// Log a launch-time warning for every checkout carrying foreign hook wiring.
///
/// Why: the operator-facing half. Named at launch so the finding lands beside
/// the session it affects.
/// What: calls [`foreign_harness_files`] and emits one `warn!` naming the files.
/// Silent when nothing is found. Never modifies a file.
/// Test: `detects_foreign_hooks_in_launch_dir` covers the detection this logs.
pub
/// The directories a launch-time scan covers.
///
/// Why: a redirected launch has TWO trees worth checking — the managed checkout
/// the session will run in, and the unmanaged directory the operator launched
/// from, which is where inherited framework state actually accumulates. Checking
/// only the session workspace would miss exactly the case that motivated this:
/// a foreign config sitting in the operator's own clone. Pure, so the
/// de-duplication is assertable without a tracing subscriber.
/// What: `[launch_dir]`, plus `workspace` when it differs.
/// Test: `launch_and_workspace_dirs_are_deduplicated`.
pub
/// Scan and warn for both directories a launch touches.
///
/// Why: the single call the spawn path makes.
/// What: [`launch_scan_dirs`] then [`warn_on_foreign_harness_config`].
/// Test: `detects_foreign_hooks_in_launch_dir`.
pub