Summary
When a repository is small enough that module clustering is skipped
("whole-repository documentation mode" — see the "Skipping LLM module
clustering ... using whole-module documentation mode" log line),
--update --compare-to <commit> correctly detects which sub-module
doc is affected by a change and deletes it, but never regenerates it.
The run always ends with:
✗ Documentation generation finished but these module docs are missing: <module>.md
IncompleteGenerationError
This is 100% reproducible, not transient — confirmed by tracing the actual
code path (details below), and confirmed twice against two different repos.
Root cause
In codewiki/src/be/documentation_generator.py,
generate_module_documentation() computes the processing order from
first_module_tree.json:
processing_order = self.get_processing_order(first_module_tree)
But first_module_tree.json is only ever written by the LLM clustering
path (cluster_modules(), in the _run_module_clustering stage). For a
repo that goes through whole-repository mode, that file stays {}
forever — even though a prior whole-repo agent run can still insert real
sub-modules into module_tree.json via
generate_sub_module_documentation_tool.
Sequence:
- First
generate run: whole-repo mode, the agent creates e.g.
module_a.md, module_b.md. module_tree.json now has 2 entries;
first_module_tree.json stays {}.
- A file under
module_a changes. generate --update --compare-to <sha>
runs. _invalidate_affected_modules() (in cli/commands/generate.py)
correctly matches the change to module_a using module_tree.json,
deletes module_a.md (and overview.md, since any invalidation also
invalidates the overview).
generate_module_documentation() loads module_tree.json
(non-empty → 2 entries) so it takes the if len(module_tree) > 0:
branch, but processing_order (from the still-empty
first_module_tree) is []. The regeneration loop runs zero
iterations — module_a is never reprocessed.
- It falls through straight to building the repo overview, which logs
Module docs not found at .../module_a.md, and then
validate_generated_docs() raises IncompleteGenerationError.
Minimal reproduction
No secrets/private code needed — a tiny throwaway repo reproduces this
every time:
src/auth/auth.service.ts, src/payment/payment.service.ts,
src/notification/notification.service.ts, src/app.ts — each a small
class with a couple of methods (~20 leaf components total, well under
the default 36,369-token clustering threshold, so whole-repo mode is
guaranteed).
git init && git add -A && git commit -m "baseline"
# <baseline_sha>
codewiki config set --provider openai-compatible --api-key <key> \
--base-url <url> --main-model <model> --cluster-model <model> \
--fallback-model <model>
codewiki generate --output ./docs
# → module_a.md / module_b.md / module_c.md / overview.md created,
# module_tree.json has 3 entries, first_module_tree.json is {}
# add one function to auth.service.ts, commit again
git add -A && git commit -m "add a function to auth service"
codewiki generate --output ./docs --update --compare-to <baseline_sha>
# → fails every time with IncompleteGenerationError
I have the exact 4 source files used and I'm opening a PR with this fix,
the fixture, and a regression test.
Suggested fix (implemented + tested in the linked PR)
Fall back to module_tree for the processing order only when
first_module_tree is empty — this only changes behavior for the
whole-repo-mode case; the normal clustered path is untouched, since
first_module_tree is non-empty there.
processing_order = self.get_processing_order(first_module_tree)
if not processing_order and module_tree:
processing_order = self.get_processing_order(module_tree)
Verified against the repro above: the affected module regenerates,
unaffected modules are correctly skipped ("✓ Module docs already
exists"), and the overview is rebuilt referencing all modules.
Summary
When a repository is small enough that module clustering is skipped
("whole-repository documentation mode" — see the "Skipping LLM module
clustering ... using whole-module documentation mode" log line),
--update --compare-to <commit>correctly detects which sub-moduledoc is affected by a change and deletes it, but never regenerates it.
The run always ends with:
This is 100% reproducible, not transient — confirmed by tracing the actual
code path (details below), and confirmed twice against two different repos.
Root cause
In
codewiki/src/be/documentation_generator.py,generate_module_documentation()computes the processing order fromfirst_module_tree.json:But
first_module_tree.jsonis only ever written by the LLM clusteringpath (
cluster_modules(), in the_run_module_clusteringstage). For arepo that goes through whole-repository mode, that file stays
{}forever — even though a prior whole-repo agent run can still insert real
sub-modules into
module_tree.jsonviagenerate_sub_module_documentation_tool.Sequence:
generaterun: whole-repo mode, the agent creates e.g.module_a.md,module_b.md.module_tree.jsonnow has 2 entries;first_module_tree.jsonstays{}.module_achanges.generate --update --compare-to <sha>runs.
_invalidate_affected_modules()(incli/commands/generate.py)correctly matches the change to
module_ausingmodule_tree.json,deletes
module_a.md(andoverview.md, since any invalidation alsoinvalidates the overview).
generate_module_documentation()loadsmodule_tree.json(non-empty → 2 entries) so it takes the
if len(module_tree) > 0:branch, but
processing_order(from the still-emptyfirst_module_tree) is[]. The regeneration loop runs zeroiterations —
module_ais never reprocessed.Module docs not found at .../module_a.md, and thenvalidate_generated_docs()raisesIncompleteGenerationError.Minimal reproduction
No secrets/private code needed — a tiny throwaway repo reproduces this
every time:
src/auth/auth.service.ts,src/payment/payment.service.ts,src/notification/notification.service.ts,src/app.ts— each a smallclass with a couple of methods (~20 leaf components total, well under
the default 36,369-token clustering threshold, so whole-repo mode is
guaranteed).
I have the exact 4 source files used and I'm opening a PR with this fix,
the fixture, and a regression test.
Suggested fix (implemented + tested in the linked PR)
Fall back to
module_treefor the processing order only whenfirst_module_treeis empty — this only changes behavior for thewhole-repo-mode case; the normal clustered path is untouched, since
first_module_treeis non-empty there.Verified against the repro above: the affected module regenerates,
unaffected modules are correctly skipped ("✓ Module docs already
exists"), and the overview is rebuilt referencing all modules.