AI Workflows: Analysis

understanding existing code before changing it

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.
CategorySpecific 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: 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.