1. Summary
When inheriting code, fixing a bug, or adding a feature to an unfamiliar module, analysis comes
before change. Asking Claude to read the code and answer specific questions produces more reliable
results than asking it to jump straight to modifications. An analysis.md file focuses the session:
it names the target files, states what you want to understand, and sets the level of detail you need.
Analysis sessions have one important constraint: Claude should report findings, not propose changes,
unless you ask. State this explicitly or the model will often blend analysis with unsolicited
suggestions.
2. analysis.md Structure
# Analysis Context
## Target
Files or modules to analyze: [list them]
## Questions
What you want to understand. Be specific - "explain this module" produces
vague output; "identify every function that modifies shared state" does not.
## Scope
Analysis only. Do not propose changes or refactors unless explicitly asked.
## Output Format
For each finding: state the result, then cite file:line.
If a question has no answer in the code, say so explicitly rather than guessing.
3. Useful Analysis Questions
The quality of analysis output depends entirely on the specificity of the question. Vague questions
produce summaries. Specific questions produce findings you can act on.
| Category | Specific question form |
| Function behavior |
"What are the preconditions for [function]? What happens if they are violated?" |
| Failure modes |
"What inputs to [function] could cause a panic, crash, or silent wrong result?" |
| Data flow |
"Trace the path from [entry point] to where results are written. Name every transformation and where it happens." |
| Invariants |
"What invariants does [struct/class] maintain? Are any of them enforced by the type system?" |
| Dead code |
"Are there any functions or branches in [file] that can never be reached on valid input?" |
| Side effects |
"Which functions in [module] have side effects beyond their return value?" |
4. Example Prompts
Function-level analysis:
Read analysis.md.
Read [file.rs].
For each public function, write one sentence describing what it does.
Then identify any function that could panic on valid input.
Cite file:line for each potential panic.
Data flow trace:
Read analysis.md.
Read [module.py].
Trace the data flow from the entry point through to where results are written
to disk. Name every intermediate transformation and the file:line where it
occurs. Do not propose any changes.
Dependency surface before a change:
Read analysis.md.
I am about to change the signature of [function] in [file].
List every caller of that function across the codebase and what each
caller expects from it. Cite file:line for each caller.
Invariant identification:
Read analysis.md.
Read [class_file].
What invariants does [ClassName] maintain across its lifetime?
Which ones are enforced by the type system and which rely on convention?
Flag any invariant that a caller could violate without a compile error.
5. Case Study: rs_textfinder Architecture
This analysis was run before adding multi-pattern text matching to rs_textfinder.
The goal was to understand the data flow, the contract at the DirNav/TfAppl boundary,
and any silent failure modes before touching the code.
analysis.md
# Analysis Context
## Target
rs_textfinder/ -- three-crate Rust text search tool:
RustCmdLine/src/cmd_line_lib.rs CLI argument parser
RustDirNav/src/dir_nav_lib.rs generic directory navigator
EntryPoint/src/text_finder.rs text search logic and main()
## Questions
1. Trace the data flow from CLI args through to printed output.
Name every transformation and the file:line where it occurs.
2. What is the contract at the DirNav/TfAppl boundary?
What does DirNav guarantee and what must TfAppl supply?
3. Which functions could panic, fail silently, or produce wrong results
on valid input? Cite file:line for each.
4. What invariants does DirNav<App> maintain?
Which are enforced by the type system and which rely on convention?
## Scope
Analysis only. Do not propose changes or refactors.
## Output Format
For each finding: state the result, then cite file:line.
If a question has no clear answer in the code, say so explicitly.
Prompt
Read analysis.md.
Read RustCmdLine/src/cmd_line_lib.rs.
Read RustDirNav/src/dir_nav_lib.rs.
Read EntryPoint/src/text_finder.rs.
Answer each question in analysis.md. For each finding, cite file:line.
Do not propose any changes.
Analysis
Q1 -- Data flow
CLI args -> CmdLineParse.parse() (cmd_line_lib.rs:193)
Scans std::env::args() left to right; '/' or '-' prefix identifies options.
/p value is comma-split into a patterns vec (cmd_line_lib.rs:215-221).
abs_path() resolves the path via fs::canonicalize(), strips the Windows \\?\
prefix, and converts backslashes to '/' (cmd_line_lib.rs:119-132).
main() configures DirNav<TfAppl> (text_finder.rs:265-291)
recurse and hide are set on both dn and the embedded TfAppl.
Extension patterns are registered with dn.add_pat().
The regex string is set on the embedded TextFinder via dn.get_app().regex().
dn.visit(&p) walks the tree depth-first (dir_nav_lib.rs:495)
skip_dirs entries are silently excluded before being entered (dir_nav_lib.rs:507-514).
app.do_dir() is called before any app.do_file() calls for that directory (dir_nav_lib.rs:522-527).
If hide is true and no files match, do_dir() is never called (dir_nav_lib.rs:522).
TfAppl::do_file() builds the full path from curr_dir + "/" + filename (text_finder.rs:95-97)
Calls TextFinder::find() on the assembled path (text_finder.rs:99).
On match: prints the directory on first match, then the filename (text_finder.rs:102-110).
main() prints the summary line after visit() returns (text_finder.rs:298-302).
Q2 -- DirNav/TfAppl contract
DirNav<App> requires App: DirEvent + Default (dir_nav_lib.rs:440).
DirEvent mandates two methods:
do_dir(&mut self, d: &str) -- receives the full normalized directory path
do_file(&mut self, f: &str) -- receives only the filename, not the full path
Guarantees from DirNav:
do_dir is called before any do_file calls for that directory (dir_nav_lib.rs:522-527).
If hide is true and no files match, do_dir is not called (dir_nav_lib.rs:522).
Burden on TfAppl:
do_file receives only the filename; TfAppl reconstructs the full path by
prepending curr_dir (text_finder.rs:95-97). curr_dir is set in do_dir.
The ordering guarantee makes this safe, but it is not enforced by the type system.
Q3 -- Failure modes
Invalid regex: TextFinder::find() returns false with no diagnostic (text_finder.rs:70).
Every file silently reports no match.
Non-existent root path: dn.visit() result is discarded with let _ = (text_finder.rs:296).
abs_path() returns the error message string on canonicalize() failure (cmd_line_lib.rs:130);
that string becomes the search root, causing visit() to fail silently.
CmdLineParse::value() panics on an unknown key (cmd_line_lib.rs:166).
Direct HashMap indexing; panics if the key was never inserted.
CmdLineParse::is_opt() indexes as_bytes()[0] without a length check (cmd_line_lib.rs:94-96).
An empty argument string produces an index-out-of-bounds panic.
Q4 -- DirNav<App> invariants
Enforced by code structure:
do_dir is called before do_file for all files in that directory (dir_nav_lib.rs:522-527).
skip_dirs entries are never entered or counted in num_dir (dir_nav_lib.rs:507-514).
Empty pats: all files trigger do_file. Non-empty: only extension-matching files (dir_nav_lib.rs:516).
Enforced by the type system:
DirEvent + Default on App is verified at compile time (dir_nav_lib.rs:440).
Convention only:
clear() preserves recurse, hide, and skip_dirs but clears pats and resets counters
(dir_nav_lib.rs:489-494). Documented contract, not a type-level guarantee.
6. Following Up
A good analysis session produces a list of findings, each with a file:line citation. The next
step depends on what you found:
- If findings reveal a misunderstanding of the design, start a fresh session with that corrected
understanding before proposing changes.
- If findings reveal a specific bug, hand the finding to a targeted fix session - state the
finding explicitly rather than asking Claude to re-derive it.
- If findings reveal a structural problem, the analysis becomes the input to a refactoring
session - see Refactoring.
Do not extend an analysis session into implementation. Start a new session with the findings
from the analysis as explicit context. This keeps the implementation session focused and prevents
the analysis from being evicted from context mid-implementation.