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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! `tm doctor` scaffold-tracking probe: harness paths BOTH tracked in git AND
//! regenerated locally (issue #3427, Option 2 — detect + exact remediation).
//!
//! Why: split out of `doctor.rs` to keep it under the 500-SLOC production
//! cap, mirroring the existing `doctor_output_style.rs` / `doctor_fs_checks.rs`
//! / `doctor_hooks_hygiene.rs` splits. [`crate::core::scaffold_gitignore`] is
//! Option 1 (prevent FUTURE commits via `.gitignore`) — it cannot fix a
//! project that ALREADY committed `.claude/agents/`, `.claude/skills/`, or
//! `.claude/output-styles/` content, because appending a `.gitignore` entry
//! does not untrack anything already in the index. This probe detects that
//! already-broken state and prints the exact `git rm -r --cached` remediation,
//! mirroring what actually fixed the verified reproduction
//! (`duettoresearch/duetto-eve-agents`, worktree `tm-duetto-eve-agents-01`,
//! stranded 28 commits — duetto-eve-agents#111, commit 0e699e2).
//!
//! The reproduction's error output (`git merge --ff-only` "would be
//! overwritten") listed far more files than the true 7-file collision,
//! because git's message lists ALL untracked content anywhere in the
//! affected tree — that breadth is what "invited over-broad cleanup" per the
//! issue. This probe never parses or echoes that git error message; instead
//! it computes the TRUE INTERSECTION directly: [`tracked_scaffold_paths`]
//! (`git ls-files`, scoped to the three harness subtrees) ∩
//! [`regenerated_scaffold_paths`] (the exact relative paths tm's own
//! manifests/bundle say it writes into `project_dir`) — and reports ONLY
//! that intersection. A project's own hand-placed, intentionally-tracked
//! "project-custom" skill (`skills::tiers::SkillTier::Project`, issue #2816 —
//! on disk under `.claude/skills/<name>/` but ABSENT from the skill
//! manifest's `managed` keys) is therefore never flagged: it is tracked but
//! not tm-regenerated, so it falls outside the intersection by construction.
//!
//! What: [`check_scaffold_tracking`] is `Ok` when `project_dir` is absent,
//! is not a git working tree, or the git-tracked ∩ tm-regenerated
//! intersection is empty; `Warn` (never auto-modifying the index) naming the
//! exact colliding paths plus a copy-pasteable `git rm -r --cached …`
//! command and the `.gitignore` entries [`crate::core::scaffold_gitignore`]
//! already knows to add. Warn-only by design (issue #3427 explicit
//! requirement): `git rm --cached` on the wrong path set is destructive to
//! the user's tracking state, so this probe never runs it — it only ever
//! reports what a human (or a follow-up, explicitly-invoked command) should
//! run.
//! Test: the `tests` module below covers the true-intersection computation
//! (collision reported, unrelated tracked project-custom skill NOT reported),
//! the clean-repo no-false-positive case, and the non-git-repo no-op case.
use BTreeSet;
use Path;
use crateAgentManifest;
use crate;
use crateSkillManifest;
/// Relative (forward-slash, `.claude`-rooted) paths tm's own manifests and
/// bundled catalog say it regenerates inside `project_dir`.
///
/// Why: this is the authoritative "paths tm regenerates" side of the
/// intersection — sourced directly from the SAME on-disk records the
/// deployers themselves maintain (`AgentManifest`/`SkillManifest`'s `managed`
/// keys) plus the fixed bundled output-style file list, rather than
/// re-deriving a full harness plan. A project-custom skill (on disk but
/// absent from `managed`) is correctly excluded here — tm never rewrites it,
/// so it is not part of "what tm regenerates" regardless of its git state.
/// What: agent paths as `.claude/agents/<managed filename>`; skill paths as
/// `.claude/skills/<stem>/SKILL.md` for a bare managed stem, or
/// `.claude/skills/<managed key>` when the key already carries the
/// `references/<file>` suffix `skills::deployer::deploy_one_file` writes
/// (mirrors that function's own `target_path` construction exactly). Output
/// styles are unconditional (issue #2125 deploys them into every project
/// tier every session, no manifest involved).
/// Test: `regenerated_paths_includes_managed_agent`,
/// `regenerated_paths_includes_skill_entry_and_references`,
/// `regenerated_paths_includes_all_output_styles`.
/// Paths under the three harness subtrees that `git` tracks in `project_dir`.
///
/// Why: scoping the `git ls-files` pathspec to exactly the three harness
/// subtrees (rather than a bare `git ls-files` over the whole repo) keeps
/// this fast even on a large monorepo and returns paths already relative to
/// `project_dir` (the `-C` cwd), matching [`regenerated_scaffold_paths`]'s
/// format directly with no further normalization.
/// What: `None` when `project_dir` is not inside a git working tree (or
/// `git` itself is unavailable) — the caller treats that as "nothing to
/// check", not a probe failure. `Some(set)` (possibly empty) otherwise.
/// Test: `tracked_scaffold_paths_returns_none_outside_git_repo`,
/// `tracked_scaffold_paths_lists_committed_files`.
/// Render the exact, copy-pasteable remediation for a non-empty collision
/// set.
///
/// Why: the issue's explicit failure mode is a misleadingly BROAD git error
/// message inviting over-broad cleanup; the fix is a command scoped to
/// exactly the true intersection, nothing more.
/// What: one `git rm -r --cached <paths…>` line (sorted, space-joined) plus a
/// reminder that `tm` keeps these paths in `.git/info/exclude` on every
/// launch (issue #3427 Part 1, #8758) so this collision does not recur once
/// the cleanup commit lands.
/// Test: `remediation_message_lists_exact_paths`.
/// Probe for harness-scaffolding paths tracked in git AND regenerated by tm
/// (issue #3427).
///
/// Why/What: see the module doc. Warn-only — never runs `git rm --cached`
/// itself; a wrong-path automated cleanup is exactly the destructive
/// footgun the issue warns against, so remediation stays a human (or
/// separately-invoked, explicit) action.
/// Test: `check_ok_when_no_project_dir`, `check_ok_when_not_a_git_repo`,
/// `check_ok_on_clean_repo`, `check_warns_on_true_collision_only`.
pub