docwright 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. ai_docgen/__init__.py +0 -0
  2. ai_docgen/analyzer.py +59 -0
  3. ai_docgen/built_in_templates/__init__.py +0 -0
  4. ai_docgen/built_in_templates/readme/__init__.py +0 -0
  5. ai_docgen/built_in_templates/readme/default.md.j2 +30 -0
  6. ai_docgen/built_in_templates/wiki/__init__.py +0 -0
  7. ai_docgen/built_in_templates/wiki/adr.md.j2 +27 -0
  8. ai_docgen/built_in_templates/wiki/api-contracts.md.j2 +20 -0
  9. ai_docgen/built_in_templates/wiki/architecture.md.j2 +25 -0
  10. ai_docgen/built_in_templates/wiki/data-model.md.j2 +22 -0
  11. ai_docgen/built_in_templates/wiki/db-schema.md.j2 +22 -0
  12. ai_docgen/built_in_templates/wiki/development-guide.md.j2 +22 -0
  13. ai_docgen/built_in_templates/wiki/integrations.md.j2 +22 -0
  14. ai_docgen/built_in_templates/wiki/operations.md.j2 +27 -0
  15. ai_docgen/built_in_templates/wiki/security.md.j2 +22 -0
  16. ai_docgen/built_in_templates/wiki/troubleshooting.md.j2 +22 -0
  17. ai_docgen/cli.py +136 -0
  18. ai_docgen/config.py +71 -0
  19. ai_docgen/engine.py +165 -0
  20. ai_docgen/outputs/__init__.py +0 -0
  21. ai_docgen/outputs/base.py +7 -0
  22. ai_docgen/outputs/direct.py +16 -0
  23. ai_docgen/outputs/factory.py +12 -0
  24. ai_docgen/outputs/pull_request.py +69 -0
  25. ai_docgen/providers/__init__.py +0 -0
  26. ai_docgen/providers/base.py +6 -0
  27. ai_docgen/providers/claude.py +22 -0
  28. ai_docgen/providers/factory.py +26 -0
  29. ai_docgen/providers/ollama.py +25 -0
  30. ai_docgen/providers/openai.py +20 -0
  31. ai_docgen/registry.py +125 -0
  32. ai_docgen/renderer.py +104 -0
  33. ai_docgen/reporters/__init__.py +0 -0
  34. ai_docgen/reporters/html.py +54 -0
  35. ai_docgen/reporters/terminal.py +31 -0
  36. ai_docgen/scaffolder.py +163 -0
  37. docwright-0.1.0.dist-info/METADATA +188 -0
  38. docwright-0.1.0.dist-info/RECORD +41 -0
  39. docwright-0.1.0.dist-info/WHEEL +4 -0
  40. docwright-0.1.0.dist-info/entry_points.txt +3 -0
  41. docwright-0.1.0.dist-info/licenses/LICENSE +21 -0
ai_docgen/__init__.py ADDED
File without changes
ai_docgen/analyzer.py ADDED
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ import fnmatch
4
+ import re
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass
9
+ class ChangedFile:
10
+ path: str
11
+
12
+
13
+ class DiffAnalyzer:
14
+ def __init__(
15
+ self,
16
+ diff_text: str,
17
+ trigger_paths: list[str],
18
+ ignore_paths: list[str],
19
+ ) -> None:
20
+ self.diff_text = diff_text
21
+ self.trigger_paths = trigger_paths
22
+ self.ignore_paths = ignore_paths
23
+
24
+ def changed_files(self) -> list[ChangedFile]:
25
+ paths: list[str] = []
26
+ for line in self.diff_text.splitlines():
27
+ match = re.match(r"^\+\+\+ b/(.+)$", line)
28
+ if match:
29
+ paths.append(match.group(1))
30
+ return [ChangedFile(path=p) for p in paths]
31
+
32
+ def has_relevant_changes(self) -> bool:
33
+ for changed in self.changed_files():
34
+ if self.is_ignored(changed.path):
35
+ continue
36
+ if self.matches_trigger(changed.path):
37
+ return True
38
+ return False
39
+
40
+ def diff_summary(self) -> str:
41
+ files = self.changed_files()
42
+ if not files:
43
+ return "No changed files."
44
+ lines = ["Changed files:"]
45
+ for f in files:
46
+ lines.append(f" - {f.path}")
47
+ lines.append("")
48
+ lines.append(self.diff_text[:3000])
49
+ if len(self.diff_text) > 3000:
50
+ lines.append("... (truncated)")
51
+ return "\n".join(lines)
52
+
53
+ def is_ignored(self, path: str) -> bool:
54
+ return any(fnmatch.fnmatch(path, pattern) for pattern in self.ignore_paths)
55
+
56
+ def matches_trigger(self, path: str) -> bool:
57
+ if not self.trigger_paths:
58
+ return True
59
+ return any(fnmatch.fnmatch(path, pattern) for pattern in self.trigger_paths)
File without changes
File without changes
@@ -0,0 +1,30 @@
1
+ # {{ service_name }}
2
+
3
+ <!-- AUTO:overview -->
4
+ <!-- /AUTO:overview -->
5
+
6
+ ## Getting Started
7
+
8
+ <!-- AUTO:getting_started -->
9
+ <!-- /AUTO:getting_started -->
10
+
11
+ ## Architecture
12
+
13
+ <!-- AUTO:architecture -->
14
+ <!-- /AUTO:architecture -->
15
+
16
+ ## API
17
+
18
+ <!-- AUTO:api -->
19
+ <!-- /AUTO:api -->
20
+
21
+ ## Development
22
+
23
+ <!-- AUTO:development -->
24
+ <!-- /AUTO:development -->
25
+
26
+ <!-- MANUAL -->
27
+ ## Contributing
28
+
29
+ Add contributing notes here.
30
+ <!-- /MANUAL -->
File without changes
@@ -0,0 +1,27 @@
1
+ # Architecture Decision Records
2
+
3
+ ## Recent Decisions
4
+
5
+ <!-- AUTO:recent_decisions -->
6
+ <!-- /AUTO:recent_decisions -->
7
+
8
+ ## Decision Index
9
+
10
+ <!-- AUTO:decision_index -->
11
+ <!-- /AUTO:decision_index -->
12
+
13
+ <!-- MANUAL -->
14
+ ## ADR Template
15
+
16
+ Use this format for new decisions:
17
+
18
+ ### ADR-NNN: Title
19
+
20
+ **Status:** Proposed / Accepted / Deprecated / Superseded
21
+
22
+ **Context:** What prompted this decision.
23
+
24
+ **Decision:** What was decided.
25
+
26
+ **Consequences:** What changes as a result.
27
+ <!-- /MANUAL -->
@@ -0,0 +1,20 @@
1
+ # API Contracts
2
+
3
+ <!-- AUTO:endpoints -->
4
+ <!-- /AUTO:endpoints -->
5
+
6
+ ## Authentication
7
+
8
+ <!-- AUTO:authentication -->
9
+ <!-- /AUTO:authentication -->
10
+
11
+ ## Error Codes
12
+
13
+ <!-- AUTO:error_codes -->
14
+ <!-- /AUTO:error_codes -->
15
+
16
+ <!-- MANUAL -->
17
+ ## Changelog
18
+
19
+ Document breaking changes here.
20
+ <!-- /MANUAL -->
@@ -0,0 +1,25 @@
1
+ # Architecture
2
+
3
+ <!-- AUTO:overview -->
4
+ <!-- /AUTO:overview -->
5
+
6
+ ## Components
7
+
8
+ <!-- AUTO:components -->
9
+ <!-- /AUTO:components -->
10
+
11
+ ## Data Flow
12
+
13
+ <!-- AUTO:data_flow -->
14
+ <!-- /AUTO:data_flow -->
15
+
16
+ ## Dependencies
17
+
18
+ <!-- AUTO:dependencies -->
19
+ <!-- /AUTO:dependencies -->
20
+
21
+ <!-- MANUAL -->
22
+ ## Decision Log
23
+
24
+ Document architectural decisions here.
25
+ <!-- /MANUAL -->
@@ -0,0 +1,22 @@
1
+ # Data Model
2
+
3
+ ## Domain Entities
4
+
5
+ <!-- AUTO:entities -->
6
+ <!-- /AUTO:entities -->
7
+
8
+ ## Business Rules
9
+
10
+ <!-- AUTO:business_rules -->
11
+ <!-- /AUTO:business_rules -->
12
+
13
+ ## Relationships
14
+
15
+ <!-- AUTO:relationships -->
16
+ <!-- /AUTO:relationships -->
17
+
18
+ <!-- MANUAL -->
19
+ ## Glossary
20
+
21
+ Define domain terms here.
22
+ <!-- /MANUAL -->
@@ -0,0 +1,22 @@
1
+ # Database Schema
2
+
3
+ ## Tables
4
+
5
+ <!-- AUTO:tables -->
6
+ <!-- /AUTO:tables -->
7
+
8
+ ## Indexes
9
+
10
+ <!-- AUTO:indexes -->
11
+ <!-- /AUTO:indexes -->
12
+
13
+ ## Migrations
14
+
15
+ <!-- AUTO:migrations -->
16
+ <!-- /AUTO:migrations -->
17
+
18
+ <!-- MANUAL -->
19
+ ## Migration Runbook
20
+
21
+ Document manual migration steps and rollback procedures here.
22
+ <!-- /MANUAL -->
@@ -0,0 +1,22 @@
1
+ # Development Guide
2
+
3
+ ## Setup
4
+
5
+ <!-- AUTO:setup -->
6
+ <!-- /AUTO:setup -->
7
+
8
+ ## Running Tests
9
+
10
+ <!-- AUTO:testing -->
11
+ <!-- /AUTO:testing -->
12
+
13
+ ## Code Style
14
+
15
+ <!-- AUTO:code_style -->
16
+ <!-- /AUTO:code_style -->
17
+
18
+ <!-- MANUAL -->
19
+ ## Team Conventions
20
+
21
+ Add team-specific conventions here.
22
+ <!-- /MANUAL -->
@@ -0,0 +1,22 @@
1
+ # Integrations
2
+
3
+ ## External Services
4
+
5
+ <!-- AUTO:external_services -->
6
+ <!-- /AUTO:external_services -->
7
+
8
+ ## Authentication & Credentials
9
+
10
+ <!-- AUTO:auth_credentials -->
11
+ <!-- /AUTO:auth_credentials -->
12
+
13
+ ## Data Exchange
14
+
15
+ <!-- AUTO:data_exchange -->
16
+ <!-- /AUTO:data_exchange -->
17
+
18
+ <!-- MANUAL -->
19
+ ## Integration Contacts
20
+
21
+ Document vendor contacts and SLA details here.
22
+ <!-- /MANUAL -->
@@ -0,0 +1,27 @@
1
+ # Operations
2
+
3
+ ## Deployment
4
+
5
+ <!-- AUTO:deployment -->
6
+ <!-- /AUTO:deployment -->
7
+
8
+ ## Monitoring
9
+
10
+ <!-- AUTO:monitoring -->
11
+ <!-- /AUTO:monitoring -->
12
+
13
+ ## Runbooks
14
+
15
+ <!-- AUTO:runbooks -->
16
+ <!-- /AUTO:runbooks -->
17
+
18
+ ## Incident Response
19
+
20
+ <!-- AUTO:incident_response -->
21
+ <!-- /AUTO:incident_response -->
22
+
23
+ <!-- MANUAL -->
24
+ ## On-Call Contacts
25
+
26
+ Document on-call rotation and escalation paths here.
27
+ <!-- /MANUAL -->
@@ -0,0 +1,22 @@
1
+ # Security
2
+
3
+ ## Access Model
4
+
5
+ <!-- AUTO:access_model -->
6
+ <!-- /AUTO:access_model -->
7
+
8
+ ## Sensitive Data Handling
9
+
10
+ <!-- AUTO:sensitive_data -->
11
+ <!-- /AUTO:sensitive_data -->
12
+
13
+ ## Security Requirements
14
+
15
+ <!-- AUTO:requirements -->
16
+ <!-- /AUTO:requirements -->
17
+
18
+ <!-- MANUAL -->
19
+ ## Security Contacts
20
+
21
+ Document responsible disclosure process and security team contacts here.
22
+ <!-- /MANUAL -->
@@ -0,0 +1,22 @@
1
+ # Troubleshooting
2
+
3
+ ## Common Issues
4
+
5
+ <!-- AUTO:common_issues -->
6
+ <!-- /AUTO:common_issues -->
7
+
8
+ ## Diagnostics
9
+
10
+ <!-- AUTO:diagnostics -->
11
+ <!-- /AUTO:diagnostics -->
12
+
13
+ ## Known Limitations
14
+
15
+ <!-- AUTO:known_limitations -->
16
+ <!-- /AUTO:known_limitations -->
17
+
18
+ <!-- MANUAL -->
19
+ ## Escalation Path
20
+
21
+ Document when and how to escalate unresolved issues here.
22
+ <!-- /MANUAL -->
ai_docgen/cli.py ADDED
@@ -0,0 +1,136 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import os
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from ai_docgen.config import Config
11
+ from ai_docgen.engine import DocsEngine
12
+ from ai_docgen.outputs.factory import build_output
13
+ from ai_docgen.providers.factory import build_provider
14
+
15
+
16
+ def get_repo_root() -> Path:
17
+ return Path.cwd()
18
+
19
+
20
+ def build_engine(repo_root: Path) -> DocsEngine:
21
+ config = Config.load(repo_root)
22
+ provider = build_provider(config.provider)
23
+ output = build_output(config.output, repo_root)
24
+ return DocsEngine(repo_root=repo_root, provider=provider, output=output)
25
+
26
+
27
+ @click.group()
28
+ def cli() -> None:
29
+ """AI-powered documentation agent."""
30
+
31
+
32
+ @cli.command()
33
+ def init() -> None:
34
+ """Generate documentation from scratch."""
35
+ engine = build_engine(get_repo_root())
36
+ asyncio.run(engine.init())
37
+ click.echo("Documentation initialized.")
38
+
39
+
40
+ @cli.command()
41
+ @click.option("--dry-run", is_flag=True, help="Show what would change without writing files.")
42
+ def run(dry_run: bool) -> None:
43
+ """Incrementally update documentation based on recent git diff."""
44
+ repo_root = get_repo_root()
45
+ base_sha = os.environ.get("AI_DOCGEN_BASE_SHA", "HEAD~1")
46
+ try:
47
+ diff_text = subprocess.check_output(
48
+ ["git", "diff", f"{base_sha}..HEAD"], cwd=repo_root
49
+ ).decode()
50
+ except subprocess.CalledProcessError:
51
+ diff_text = ""
52
+ if dry_run:
53
+ config = Config.load(repo_root)
54
+ triggers = config.triggers
55
+ from ai_docgen.analyzer import DiffAnalyzer
56
+
57
+ analyzer = DiffAnalyzer(
58
+ diff_text=diff_text,
59
+ trigger_paths=triggers.paths if triggers else [],
60
+ ignore_paths=triggers.ignore if triggers else [],
61
+ )
62
+ if analyzer.has_relevant_changes():
63
+ click.echo("Relevant changes detected — documentation would be updated.")
64
+ else:
65
+ click.echo("No relevant changes — documentation would be skipped.")
66
+ return
67
+ engine = build_engine(repo_root)
68
+ skipped = asyncio.run(engine.run(diff_text=diff_text))
69
+ click.echo(
70
+ "No relevant changes — documentation up to date." if skipped else "Documentation updated."
71
+ )
72
+
73
+
74
+ @cli.command()
75
+ def sync() -> None:
76
+ """Force re-sync all documentation against current templates."""
77
+ engine = build_engine(get_repo_root())
78
+ asyncio.run(engine.sync())
79
+ click.echo("Documentation synced.")
80
+
81
+
82
+ @cli.command("install")
83
+ @click.option("--auto", is_flag=True, help="Non-interactive mode with auto-detected defaults.")
84
+ @click.option("--provider", default=None, type=click.Choice(["claude", "openai", "ollama"]))
85
+ @click.option("--output", "output_mode", default=None, type=click.Choice(["pr", "direct"]))
86
+ def install(auto: bool, provider: str | None, output_mode: str | None) -> None:
87
+ """Bootstrap this repo with ai-docgen configuration."""
88
+ from ai_docgen.scaffolder import Scaffolder
89
+
90
+ repo_root = get_repo_root()
91
+ scaffolder = Scaffolder(repo_root=repo_root)
92
+ profile = scaffolder.detect_profile()
93
+
94
+ if auto:
95
+ final_provider = provider or "claude"
96
+ final_output = output_mode or "pr"
97
+ else:
98
+ click.echo(
99
+ f"Detected: {profile.language} project '{profile.service_name}', CI: {profile.ci}"
100
+ )
101
+ final_provider = provider or click.prompt(
102
+ "LLM provider",
103
+ type=click.Choice(["claude", "openai", "ollama"]),
104
+ default="claude",
105
+ )
106
+ final_output = output_mode or click.prompt(
107
+ "Output mode",
108
+ type=click.Choice(["pr", "direct"]),
109
+ default="pr",
110
+ )
111
+
112
+ scaffolder.generate(profile, provider_type=final_provider, output_mode=final_output)
113
+ click.echo(f"Installed ai-docgen for '{profile.service_name}'.")
114
+ click.echo("Next: set your API key env var, then run 'make docs'.")
115
+
116
+
117
+ @cli.command()
118
+ @click.option("--registry", "registry_path", default=None, help="Path to registry.yml")
119
+ def dashboard(registry_path: str | None) -> None:
120
+ """Show status of all registered projects."""
121
+ from ai_docgen.reporters.terminal import render_dashboard
122
+
123
+ path = Path(registry_path) if registry_path else Path.cwd() / ".ai-docgen" / "registry.yml"
124
+ render_dashboard(path)
125
+
126
+
127
+ @cli.command()
128
+ @click.option("--registry", "registry_path", default=None, help="Path to registry.yml")
129
+ @click.option("--output", "output_file", default="ai-docgen-report.html", help="Output HTML file")
130
+ def report(registry_path: str | None, output_file: str) -> None:
131
+ """Generate a static HTML status report."""
132
+ from ai_docgen.reporters.html import render_html_report
133
+
134
+ reg_path = Path(registry_path) if registry_path else Path.cwd() / ".ai-docgen" / "registry.yml"
135
+ render_html_report(reg_path, Path(output_file))
136
+ click.echo(f"Report saved to {output_file}")
ai_docgen/config.py ADDED
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Literal
5
+
6
+ import yaml
7
+ from pydantic import BaseModel, Field
8
+
9
+ CONFIG_DIR = ".ai-docgen"
10
+ CONFIG_FILE = "ai-docgen.yml"
11
+ INITIALIZED_MARKER = ".initialized"
12
+
13
+
14
+ class ProviderConfig(BaseModel):
15
+ type: Literal["claude", "openai", "ollama"]
16
+ model: str
17
+ api_key_env: str = "ANTHROPIC_API_KEY"
18
+ base_url: str | None = None
19
+
20
+
21
+ class OutputConfig(BaseModel):
22
+ mode: Literal["direct", "pr"] = "pr"
23
+ pr_title: str = "docs: auto-update documentation"
24
+ branch_prefix: str = "docs/auto-"
25
+
26
+
27
+ class TemplatesConfig(BaseModel):
28
+ source: Literal["builtin", "local"] = "builtin"
29
+ local_path: str = ".ai-docgen/templates"
30
+
31
+
32
+ class TriggersConfig(BaseModel):
33
+ paths: list[str] = Field(default_factory=lambda: ["app/**", "src/**"])
34
+ ignore: list[str] = Field(default_factory=lambda: ["tests/**", "**/*.md"])
35
+
36
+
37
+ class DocumentConfig(BaseModel):
38
+ type: Literal["readme", "wiki"]
39
+ template: str
40
+ target: str
41
+
42
+
43
+ class RegistryConfig(BaseModel):
44
+ path: str = "../.ai-docgen/registry.yml"
45
+
46
+
47
+ class Config(BaseModel):
48
+ provider: ProviderConfig
49
+ output: OutputConfig = Field(default_factory=OutputConfig)
50
+ templates: TemplatesConfig = Field(default_factory=TemplatesConfig)
51
+ triggers: TriggersConfig | None = None
52
+ documents: list[DocumentConfig] = Field(default_factory=list)
53
+ registry: RegistryConfig = Field(default_factory=RegistryConfig)
54
+
55
+ @classmethod
56
+ def load(cls, repo_root: Path) -> Config:
57
+ config_path = repo_root / CONFIG_DIR / CONFIG_FILE
58
+ if not config_path.exists():
59
+ raise FileNotFoundError(f"Config not found: {config_path}")
60
+ data = yaml.safe_load(config_path.read_text())
61
+ return cls.model_validate(data)
62
+
63
+ @classmethod
64
+ def is_initialized(cls, repo_root: Path) -> bool:
65
+ return (repo_root / CONFIG_DIR / INITIALIZED_MARKER).exists()
66
+
67
+ @classmethod
68
+ def mark_initialized(cls, repo_root: Path) -> None:
69
+ marker = repo_root / CONFIG_DIR / INITIALIZED_MARKER
70
+ marker.parent.mkdir(parents=True, exist_ok=True)
71
+ marker.touch()