Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ tests/*
!tests/test_min_module_size.py
!tests/test_doc_write_guard.py
!tests/test_overview_generation.py
!tests/test_techdocs.py

# Jupyter
*.ipynb
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ uses [Semantic Versioning](https://semver.org/).

### Added

- **Backstage TechDocs output.** `codewiki generate --techdocs` (or
`codewiki techdocs DOCS_DIR OUT_DIR` on existing docs, with no LLM calls)
writes an MkDocs project that `techdocs-cli generate` builds as is:
`mkdocs.yml` with the `techdocs-core` plugin and the module tree as the nav,
plus `docs/` with the overview as `index.md`. Each page gets front matter, a
provenance banner (commit, date, model) and a collapsible list of the
components it documents, linked to the source at the documented commit.
JSON files and `temp/` are left out
([#145](https://github.com/FSoft-AI4Code/CodeWiki/issues/145)).
- **Google provider.** `google` (Gemini API key) and `google-vertex` (Vertex AI
with Google credentials: ADC or Workload Identity Federation, plus
`--google-project` and `--google-location`) run on Gemini without a base
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Atlas Cloud, and Codex are also supported. See [Providers and models](./guides/p
cd /path/to/your/project
codewiki generate # writes ./docs/
codewiki generate --github-pages # also writes an HTML viewer
codewiki generate --techdocs # also exports a Backstage TechDocs site to ./techdocs
codewiki generate --update # later: refresh after code changes
```

Expand Down Expand Up @@ -181,6 +182,12 @@ its sub-modules (`auth.md`, `auth/login.md`). `--flat` puts every page in
`./docs` instead, which helps small models that get relative links wrong.
`--update` keeps the layout the docs were generated with.

`--techdocs` (or `codewiki techdocs` on existing docs) also writes a
[Backstage TechDocs](https://backstage.io/docs/features/techdocs/) project,
`./techdocs/mkdocs.yml` plus `./techdocs/docs/`, with `overview.md` as
`index.md` and the module tree as the navigation. See
[Backstage TechDocs](./guides/backstage-techdocs.md).

This repository's own output is checked in under [`./docs/`](./docs/).

## Benchmark results
Expand Down Expand Up @@ -244,6 +251,7 @@ JavaScript, TypeScript). Artifacts lift the build and testing topics, which
| [Artifact-aware generation](./guides/artifact-aware-generation.md) | what gets documented beyond code, and how to tune it |
| [Incremental updates](./guides/incremental-updates.md) | how `--update` works, thresholds, the update record |
| [MCP / IDE-driven mode](./guides/mcp-ide-mode.md) | Cursor, Claude Desktop, Claude Code, CodeBuddy |
| [Backstage TechDocs](./guides/backstage-techdocs.md) | publish the docs in a Backstage developer portal |
| [Development guide](./guides/development.md) | layout, pipeline, adding a language, tests, releasing |
| [Docker setup](./guides/docker.md) | the web application in a container |
| [CodeWikiBench](https://github.com/FSoft-AI4Code/CodeWikiBench) | the benchmark |
Expand Down
40 changes: 40 additions & 0 deletions codewiki/cli/adapters/doc_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def __init__(
verbose: bool = False,
generate_html: bool = False,
commit_id: str | None = None,
techdocs_dir: Path | None = None,
):
"""
Initialize the CLI documentation generator.
Expand All @@ -58,13 +59,16 @@ def __init__(
config: LLM configuration
verbose: Enable verbose output
generate_html: Whether to generate HTML viewer
techdocs_dir: Export a Backstage TechDocs project here (None: don't)
commit_id: Git commit SHA for incremental update tracking
"""
self.repo_path = repo_path
self.output_dir = output_dir
self.config = config
self.verbose = verbose
self.generate_html = generate_html
self.techdocs_dir = techdocs_dir
self.techdocs_result = None
self.commit_id = commit_id
self.progress_tracker = ProgressTracker(total_stages=5, verbose=verbose)
self.job = DocumentationJob()
Expand Down Expand Up @@ -181,6 +185,9 @@ def generate(self) -> DocumentationJob:
if self.generate_html:
self._run_html_generation()

if self.techdocs_dir is not None:
self._run_techdocs_generation()

# Stage 5: Finalization (metadata already created by backend)
self._finalize_job()

Expand Down Expand Up @@ -583,6 +590,39 @@ def _run_html_generation(self):

self.progress_tracker.complete_stage()

def _run_techdocs_generation(self):
"""Export the docs as a Backstage TechDocs project (no LLM calls)."""
self.progress_tracker.start_stage(4, "TechDocs Export")

from codewiki.cli.techdocs_generator import detect_repo_url, generate_techdocs

try:
self.techdocs_result = generate_techdocs(
docs_dir=self.output_dir,
out_dir=self.techdocs_dir,
site_name=self.repo_path.name,
repo_url=detect_repo_url(str(self.repo_path)),
)
except (OSError, ValueError) as e:
# The docs are already written; a failed export must not fail the run
import click

click.secho(
f"⚠ TechDocs export failed: {e}\n"
f" Retry with: codewiki techdocs {self.output_dir} {self.techdocs_dir}",
fg="yellow",
err=True,
)
self.progress_tracker.complete_stage()
return

if self.verbose:
self.progress_tracker.update_stage(
1.0, f"Wrote {self.techdocs_result.pages} pages to {self.techdocs_dir}"
)

self.progress_tracker.complete_stage()

def _record_usage_statistics(self):
"""Copy the run's token and cost totals from the usage ledger into the job."""
if self._usage_ledger is None:
Expand Down
30 changes: 30 additions & 0 deletions codewiki/cli/commands/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,18 @@ def _find_affected(tree, parent_names=None):
is_flag=True,
help="Generate index.html for GitHub Pages deployment",
)
@click.option(
"--techdocs",
is_flag=True,
help="Also export a Backstage TechDocs project (mkdocs.yml + docs/) to --techdocs-dir",
)
@click.option(
"--techdocs-dir",
type=click.Path(file_okay=False),
default="techdocs",
show_default=True,
help="Where --techdocs writes the TechDocs project (outside the --output directory)",
)
@click.option(
"--no-cache",
is_flag=True,
Expand Down Expand Up @@ -459,6 +471,8 @@ def generate_command(
output: str,
create_branch: bool,
github_pages: bool,
techdocs: bool,
techdocs_dir: str,
no_cache: bool,
include: str | None,
exclude: str | None,
Expand Down Expand Up @@ -512,6 +526,10 @@ def generate_command(
# With git branch creation and GitHub Pages
$ codewiki generate --create-branch --github-pages

\b
# Also export a Backstage TechDocs project to ./techdocs
$ codewiki generate --techdocs

\b
# Force full regeneration
$ codewiki generate --no-cache
Expand Down Expand Up @@ -606,6 +624,16 @@ def generate_command(
# Validate output directory
output_dir = Path(output).expanduser().resolve()
check_writable_output(output_dir.parent)
techdocs_out = None
if techdocs:
from codewiki.cli.techdocs_generator import check_output_dir

techdocs_out = Path(techdocs_dir).expanduser().resolve()
# Fail before the LLM run, not after it
try:
check_output_dir(techdocs_out, output_dir)
except ValueError as e:
raise ConfigurationError(f"--techdocs-dir: {e}") from e

logger.success(f"Output directory: {output_dir}")

Expand Down Expand Up @@ -854,6 +882,7 @@ def generate_command(
},
verbose=verbose,
generate_html=github_pages,
techdocs_dir=techdocs_out,
commit_id=commit_id,
)

Expand Down Expand Up @@ -883,6 +912,7 @@ def generate_command(
repo_url=repo_url,
branch_name=branch_name,
github_pages=github_pages,
techdocs=generator.techdocs_result,
files_generated=job.files_generated,
statistics={
"module_count": job.module_count,
Expand Down
75 changes: 75 additions & 0 deletions codewiki/cli/commands/techdocs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
`codewiki techdocs`: export generated docs as a Backstage TechDocs source tree.
"""

import sys
from pathlib import Path

import click

from codewiki.cli.techdocs_generator import TechDocsResult, generate_techdocs


def echo_techdocs_next_steps(result: TechDocsResult) -> None:
"""How to build and register the exported TechDocs site."""
click.echo(" Build it:")
etag = f" --etag {result.commit}" if result.commit else ""
click.secho(f" techdocs-cli generate --source-dir {result.out_dir}{etag}", fg="yellow")
click.echo(" Or point the catalog entity at it (catalog-info.yaml):")
click.secho(f" backstage.io/techdocs-ref: dir:./{result.out_dir.name}", fg="yellow")


@click.command(name="techdocs")
@click.argument(
"docs_dir",
type=click.Path(file_okay=False, path_type=Path),
default="docs",
)
@click.argument(
"out_dir",
type=click.Path(file_okay=False, path_type=Path),
default="techdocs",
)
@click.option("--site-name", default=None, help="Site name (default: repository folder name)")
@click.option("--repo-url", default=None, help="https repository URL (default: git origin)")
@click.option(
"--source-url-template",
default=None,
help="Source link format with {repo}, {ref} and {path}, e.g. for Bitbucket Server: "
"'https://bb.example.com/projects/P/repos/R/browse/{path}?at={ref}'. "
"GitHub, GitLab and bitbucket.org are detected automatically.",
)
def techdocs_command(
docs_dir: Path,
out_dir: Path,
site_name: str | None,
repo_url: str | None,
source_url_template: str | None,
):
"""Export CodeWiki docs as a Backstage TechDocs (MkDocs) project.

Reads the pages in DOCS_DIR (default: ./docs) and writes mkdocs.yml and
docs/ to OUT_DIR (default: ./techdocs). No LLM is called, so run it again
after every `codewiki generate --update`. The source docs are not changed.

\b
Examples:
$ codewiki techdocs
$ codewiki techdocs docs techdocs --site-name "Payments API"
"""
docs_dir = docs_dir.expanduser().resolve()
out_dir = out_dir.expanduser().resolve()
try:
result = generate_techdocs(
docs_dir,
out_dir,
site_name=site_name,
repo_url=repo_url,
source_template=source_url_template,
)
except (FileNotFoundError, ValueError) as e:
click.secho(f"✗ {e}", fg="red", err=True)
sys.exit(1)

click.secho(f"✓ Wrote {result.pages} TechDocs pages to {result.out_dir}", fg="green")
echo_techdocs_next_steps(result)
2 changes: 2 additions & 0 deletions codewiki/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from codewiki import __version__
from codewiki.cli.commands.config import config_group
from codewiki.cli.commands.generate import generate_command
from codewiki.cli.commands.techdocs import techdocs_command
from codewiki.cli.utils.branding import print_banner


Expand Down Expand Up @@ -43,6 +44,7 @@ def version():
# Register command groups
cli.add_command(config_group)
cli.add_command(generate_command, name="generate")
cli.add_command(techdocs_command, name="techdocs")


@cli.command(name="mcp")
Expand Down
Loading
Loading