docwright 0.1.3__tar.gz → 0.1.5__tar.gz
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.
- {docwright-0.1.3 → docwright-0.1.5}/PKG-INFO +1 -1
- {docwright-0.1.3 → docwright-0.1.5}/docwright/config.py +1 -0
- docwright-0.1.5/docwright/engine.py +290 -0
- {docwright-0.1.3 → docwright-0.1.5}/pyproject.toml +1 -1
- docwright-0.1.3/docwright/engine.py +0 -165
- {docwright-0.1.3 → docwright-0.1.5}/LICENSE +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/README.md +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/__init__.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/analyzer.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/__init__.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/readme/__init__.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/readme/default.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/__init__.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/adr.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/api-contracts.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/architecture.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/data-model.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/db-schema.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/development-guide.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/home.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/integrations.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/operations.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/security.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/troubleshooting.md.j2 +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/cli.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/outputs/__init__.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/outputs/base.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/outputs/direct.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/outputs/factory.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/outputs/pull_request.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/providers/__init__.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/providers/base.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/providers/claude.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/providers/factory.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/providers/ollama.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/providers/openai.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/registry.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/renderer.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/reporters/__init__.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/reporters/html.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/reporters/terminal.py +0 -0
- {docwright-0.1.3 → docwright-0.1.5}/docwright/scaffolder.py +0 -0
|
@@ -51,6 +51,7 @@ class Config(BaseModel):
|
|
|
51
51
|
triggers: TriggersConfig | None = None
|
|
52
52
|
documents: list[DocumentConfig] = Field(default_factory=list)
|
|
53
53
|
registry: RegistryConfig = Field(default_factory=RegistryConfig)
|
|
54
|
+
language: str = "en"
|
|
54
55
|
|
|
55
56
|
@classmethod
|
|
56
57
|
def load(cls, repo_root: Path) -> Config:
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
9
|
+
|
|
10
|
+
from docwright.analyzer import DiffAnalyzer
|
|
11
|
+
from docwright.config import Config
|
|
12
|
+
from docwright.outputs.base import Output
|
|
13
|
+
from docwright.providers.base import LLMProvider
|
|
14
|
+
from docwright.registry import DocumentEntry, ProjectEntry, Registry
|
|
15
|
+
from docwright.renderer import DocumentRenderer, TemplateLoader
|
|
16
|
+
|
|
17
|
+
SYSTEM_PROMPT_TEMPLATE = (
|
|
18
|
+
"You are a technical documentation writer. You update specific sections of documentation "
|
|
19
|
+
"based on code changes. Return ONLY the updated section content — no markdown fences, "
|
|
20
|
+
"no explanations, no surrounding text. Write in clear, concise English. "
|
|
21
|
+
"Be accurate and specific to the actual code. "
|
|
22
|
+
"{language_instruction}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
_console = Console(stderr=False)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_system_prompt(language: str) -> str:
|
|
29
|
+
if language == "en":
|
|
30
|
+
language_instruction = "Generate all documentation in English language."
|
|
31
|
+
elif language == "ru":
|
|
32
|
+
language_instruction = "Generate all documentation in Russian language."
|
|
33
|
+
else:
|
|
34
|
+
language_instruction = f"Generate all documentation in {language} language."
|
|
35
|
+
return SYSTEM_PROMPT_TEMPLATE.format(language_instruction=language_instruction)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DocsEngine:
|
|
39
|
+
def __init__(self, repo_root: Path, provider: LLMProvider, output: Output) -> None:
|
|
40
|
+
self.repo_root = repo_root
|
|
41
|
+
self.provider = provider
|
|
42
|
+
self.output = output
|
|
43
|
+
self.config = Config.load(repo_root)
|
|
44
|
+
self.renderer = DocumentRenderer()
|
|
45
|
+
self.loader = TemplateLoader(
|
|
46
|
+
source=self.config.templates.source,
|
|
47
|
+
local_path=(
|
|
48
|
+
repo_root / self.config.templates.local_path
|
|
49
|
+
if self.config.templates.source == "local"
|
|
50
|
+
else None
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
self.system_prompt = build_system_prompt(self.config.language)
|
|
54
|
+
|
|
55
|
+
async def init(self) -> None:
|
|
56
|
+
total_start = time.monotonic()
|
|
57
|
+
changed_files: list[Path] = []
|
|
58
|
+
sections_count = 0
|
|
59
|
+
|
|
60
|
+
with Progress(
|
|
61
|
+
SpinnerColumn(),
|
|
62
|
+
TextColumn("[blue]{task.description}"),
|
|
63
|
+
console=_console,
|
|
64
|
+
transient=True,
|
|
65
|
+
) as progress:
|
|
66
|
+
ctx_task = progress.add_task("Reading repository context...")
|
|
67
|
+
repo_context = self.gather_repo_context()
|
|
68
|
+
progress.remove_task(ctx_task)
|
|
69
|
+
|
|
70
|
+
_console.print("[green]✓[/green] Repository context ready")
|
|
71
|
+
|
|
72
|
+
for doc_config in self.config.documents:
|
|
73
|
+
target = self.repo_root / doc_config.target
|
|
74
|
+
template_text = self.loader.load(doc_config.template)
|
|
75
|
+
document = template_text
|
|
76
|
+
section_names = self.renderer.auto_section_names(template_text)
|
|
77
|
+
|
|
78
|
+
for section_name in section_names:
|
|
79
|
+
with Progress(
|
|
80
|
+
SpinnerColumn(),
|
|
81
|
+
TextColumn("[blue]{task.description}"),
|
|
82
|
+
console=_console,
|
|
83
|
+
transient=True,
|
|
84
|
+
) as progress:
|
|
85
|
+
task = progress.add_task(f"Generating {doc_config.target} → {section_name}...")
|
|
86
|
+
section_start = time.monotonic()
|
|
87
|
+
user_prompt = (
|
|
88
|
+
f"Repository context:\n{repo_context}\n\n"
|
|
89
|
+
f"Document type: {doc_config.type}\n"
|
|
90
|
+
f"Update the '{section_name}' section with accurate, detailed information."
|
|
91
|
+
)
|
|
92
|
+
updated_content = await self.provider.complete(
|
|
93
|
+
system=self.system_prompt, user=user_prompt
|
|
94
|
+
)
|
|
95
|
+
elapsed = time.monotonic() - section_start
|
|
96
|
+
document = self.renderer.patch_section(
|
|
97
|
+
document, section_name, updated_content + "\n"
|
|
98
|
+
)
|
|
99
|
+
progress.remove_task(task)
|
|
100
|
+
|
|
101
|
+
_console.print(
|
|
102
|
+
f"[green]✓[/green] {doc_config.target} → {section_name} ({elapsed:.1f}s)"
|
|
103
|
+
)
|
|
104
|
+
sections_count += 1
|
|
105
|
+
|
|
106
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
target.write_text(document)
|
|
108
|
+
changed_files.append(target)
|
|
109
|
+
|
|
110
|
+
if changed_files:
|
|
111
|
+
self.output.apply(changed_files, "docs: generate initial documentation")
|
|
112
|
+
|
|
113
|
+
Config.mark_initialized(self.repo_root)
|
|
114
|
+
self.register_in_registry()
|
|
115
|
+
|
|
116
|
+
total_elapsed = time.monotonic() - total_start
|
|
117
|
+
_console.print(
|
|
118
|
+
f"[green]✓[/green] Initialized {sections_count} documents in {total_elapsed:.1f}s"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
async def run(self, diff_text: str) -> bool:
|
|
122
|
+
if not Config.is_initialized(self.repo_root):
|
|
123
|
+
await self.init()
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
total_start = time.monotonic()
|
|
127
|
+
triggers = self.config.triggers
|
|
128
|
+
analyzer = DiffAnalyzer(
|
|
129
|
+
diff_text=diff_text,
|
|
130
|
+
trigger_paths=triggers.paths if triggers else [],
|
|
131
|
+
ignore_paths=triggers.ignore if triggers else [],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
with Progress(
|
|
135
|
+
SpinnerColumn(),
|
|
136
|
+
TextColumn("[blue]{task.description}"),
|
|
137
|
+
console=_console,
|
|
138
|
+
transient=True,
|
|
139
|
+
) as progress:
|
|
140
|
+
task = progress.add_task("Analyzing diff...")
|
|
141
|
+
has_changes = analyzer.has_relevant_changes()
|
|
142
|
+
progress.remove_task(task)
|
|
143
|
+
|
|
144
|
+
if not has_changes:
|
|
145
|
+
_console.print("[green]✓[/green] No relevant changes, skipping")
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
changed_file_paths = analyzer.changed_files() if hasattr(analyzer, "changed_files") else []
|
|
149
|
+
_console.print(
|
|
150
|
+
f"[green]✓[/green] Found relevant changes in {len(changed_file_paths)} files"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
changed_files: list[Path] = []
|
|
154
|
+
diff_summary = analyzer.diff_summary()
|
|
155
|
+
sections_count = 0
|
|
156
|
+
|
|
157
|
+
for doc_config in self.config.documents:
|
|
158
|
+
target = self.repo_root / doc_config.target
|
|
159
|
+
if not target.exists():
|
|
160
|
+
continue
|
|
161
|
+
document = target.read_text()
|
|
162
|
+
section_names = self.renderer.auto_section_names(document)
|
|
163
|
+
updated = False
|
|
164
|
+
|
|
165
|
+
for section_name in section_names:
|
|
166
|
+
with Progress(
|
|
167
|
+
SpinnerColumn(),
|
|
168
|
+
TextColumn("[blue]{task.description}"),
|
|
169
|
+
console=_console,
|
|
170
|
+
transient=True,
|
|
171
|
+
) as progress:
|
|
172
|
+
task = progress.add_task(f"Generating {doc_config.target} → {section_name}...")
|
|
173
|
+
section_start = time.monotonic()
|
|
174
|
+
user_prompt = (
|
|
175
|
+
f"Diff summary:\n{diff_summary}\n\n"
|
|
176
|
+
f"Current document:\n{document}\n\n"
|
|
177
|
+
f"Update the '{section_name}' section if the diff affects it. "
|
|
178
|
+
f"If no update is needed, return the current section content unchanged."
|
|
179
|
+
)
|
|
180
|
+
updated_content = await self.provider.complete(
|
|
181
|
+
system=self.system_prompt, user=user_prompt
|
|
182
|
+
)
|
|
183
|
+
elapsed = time.monotonic() - section_start
|
|
184
|
+
new_document = self.renderer.patch_section(
|
|
185
|
+
document, section_name, updated_content + "\n"
|
|
186
|
+
)
|
|
187
|
+
progress.remove_task(task)
|
|
188
|
+
|
|
189
|
+
_console.print(
|
|
190
|
+
f"[green]✓[/green] {doc_config.target} → {section_name} ({elapsed:.1f}s)"
|
|
191
|
+
)
|
|
192
|
+
sections_count += 1
|
|
193
|
+
|
|
194
|
+
if new_document != document:
|
|
195
|
+
document = new_document
|
|
196
|
+
updated = True
|
|
197
|
+
|
|
198
|
+
if updated:
|
|
199
|
+
target.write_text(document)
|
|
200
|
+
changed_files.append(target)
|
|
201
|
+
|
|
202
|
+
if changed_files:
|
|
203
|
+
self.output.apply(changed_files, "docs: update documentation")
|
|
204
|
+
|
|
205
|
+
total_elapsed = time.monotonic() - total_start
|
|
206
|
+
_console.print(
|
|
207
|
+
f"[green]✓[/green] Updated {sections_count} sections in {total_elapsed:.1f}s"
|
|
208
|
+
)
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
async def sync(self) -> None:
|
|
212
|
+
total_start = time.monotonic()
|
|
213
|
+
sections_count = 0
|
|
214
|
+
|
|
215
|
+
for doc_config in self.config.documents:
|
|
216
|
+
target = self.repo_root / doc_config.target
|
|
217
|
+
template_text = self.loader.load(doc_config.template)
|
|
218
|
+
document = target.read_text() if target.exists() else template_text
|
|
219
|
+
section_names = self.renderer.auto_section_names(template_text)
|
|
220
|
+
repo_context = self.gather_repo_context()
|
|
221
|
+
|
|
222
|
+
for section_name in section_names:
|
|
223
|
+
with Progress(
|
|
224
|
+
SpinnerColumn(),
|
|
225
|
+
TextColumn("[blue]{task.description}"),
|
|
226
|
+
console=_console,
|
|
227
|
+
transient=True,
|
|
228
|
+
) as progress:
|
|
229
|
+
task = progress.add_task(f"Syncing {doc_config.target} → {section_name}...")
|
|
230
|
+
section_start = time.monotonic()
|
|
231
|
+
user_prompt = (
|
|
232
|
+
f"Repository context:\n{repo_context}\n\n"
|
|
233
|
+
f"Current document:\n{document}\n\n"
|
|
234
|
+
f"Re-sync the '{section_name}' section to be accurate and up to date."
|
|
235
|
+
)
|
|
236
|
+
updated_content = await self.provider.complete(
|
|
237
|
+
system=self.system_prompt, user=user_prompt
|
|
238
|
+
)
|
|
239
|
+
elapsed = time.monotonic() - section_start
|
|
240
|
+
document = self.renderer.patch_section(
|
|
241
|
+
document, section_name, updated_content + "\n"
|
|
242
|
+
)
|
|
243
|
+
progress.remove_task(task)
|
|
244
|
+
|
|
245
|
+
_console.print(
|
|
246
|
+
f"[green]✓[/green] {doc_config.target} → {section_name} ({elapsed:.1f}s)"
|
|
247
|
+
)
|
|
248
|
+
sections_count += 1
|
|
249
|
+
|
|
250
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
251
|
+
target.write_text(document)
|
|
252
|
+
|
|
253
|
+
total_elapsed = time.monotonic() - total_start
|
|
254
|
+
_console.print(f"[green]✓[/green] Synced {sections_count} sections in {total_elapsed:.1f}s")
|
|
255
|
+
|
|
256
|
+
def gather_repo_context(self) -> str:
|
|
257
|
+
lines: list[str] = [f"Repo root: {self.repo_root.name}"]
|
|
258
|
+
for candidate in ["pyproject.toml", "package.json", "composer.json", "go.mod"]:
|
|
259
|
+
path = self.repo_root / candidate
|
|
260
|
+
if path.exists():
|
|
261
|
+
lines.append(f"\n{candidate}:\n{path.read_text()[:2000]}")
|
|
262
|
+
break
|
|
263
|
+
src_dirs = ["app", "src", "lib"]
|
|
264
|
+
for src_dir in src_dirs:
|
|
265
|
+
full = self.repo_root / src_dir
|
|
266
|
+
if full.exists():
|
|
267
|
+
files = [str(p.relative_to(self.repo_root)) for p in full.rglob("*.py")][:20]
|
|
268
|
+
lines.append(f"\nSource files in {src_dir}/: {', '.join(files)}")
|
|
269
|
+
break
|
|
270
|
+
return "\n".join(lines)
|
|
271
|
+
|
|
272
|
+
def register_in_registry(self) -> None:
|
|
273
|
+
registry_path = self.repo_root / self.config.registry.path
|
|
274
|
+
registry = Registry(registry_path)
|
|
275
|
+
try:
|
|
276
|
+
remote = (
|
|
277
|
+
subprocess.check_output(["git", "remote", "get-url", "origin"], cwd=self.repo_root)
|
|
278
|
+
.decode()
|
|
279
|
+
.strip()
|
|
280
|
+
)
|
|
281
|
+
except subprocess.CalledProcessError:
|
|
282
|
+
remote = ""
|
|
283
|
+
registry.register(
|
|
284
|
+
ProjectEntry(
|
|
285
|
+
name=self.repo_root.name,
|
|
286
|
+
path=str(self.repo_root),
|
|
287
|
+
remote=remote,
|
|
288
|
+
documents=[DocumentEntry(target=d.target) for d in self.config.documents],
|
|
289
|
+
)
|
|
290
|
+
)
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import subprocess
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from docwright.analyzer import DiffAnalyzer
|
|
7
|
-
from docwright.config import Config
|
|
8
|
-
from docwright.outputs.base import Output
|
|
9
|
-
from docwright.providers.base import LLMProvider
|
|
10
|
-
from docwright.registry import DocumentEntry, ProjectEntry, Registry
|
|
11
|
-
from docwright.renderer import DocumentRenderer, TemplateLoader
|
|
12
|
-
|
|
13
|
-
SYSTEM_PROMPT = (
|
|
14
|
-
"You are a technical documentation writer. You update specific sections of documentation "
|
|
15
|
-
"based on code changes. Return ONLY the updated section content — no markdown fences, "
|
|
16
|
-
"no explanations, no surrounding text. Write in clear, concise English. "
|
|
17
|
-
"Be accurate and specific to the actual code."
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class DocsEngine:
|
|
22
|
-
def __init__(self, repo_root: Path, provider: LLMProvider, output: Output) -> None:
|
|
23
|
-
self.repo_root = repo_root
|
|
24
|
-
self.provider = provider
|
|
25
|
-
self.output = output
|
|
26
|
-
self.config = Config.load(repo_root)
|
|
27
|
-
self.renderer = DocumentRenderer()
|
|
28
|
-
self.loader = TemplateLoader(
|
|
29
|
-
source=self.config.templates.source,
|
|
30
|
-
local_path=(
|
|
31
|
-
repo_root / self.config.templates.local_path
|
|
32
|
-
if self.config.templates.source == "local"
|
|
33
|
-
else None
|
|
34
|
-
),
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
async def init(self) -> None:
|
|
38
|
-
changed_files: list[Path] = []
|
|
39
|
-
for doc_config in self.config.documents:
|
|
40
|
-
target = self.repo_root / doc_config.target
|
|
41
|
-
template_text = self.loader.load(doc_config.template)
|
|
42
|
-
document = target.read_text() if target.exists() else template_text
|
|
43
|
-
section_names = self.renderer.auto_section_names(template_text)
|
|
44
|
-
repo_context = self.gather_repo_context()
|
|
45
|
-
for section_name in section_names:
|
|
46
|
-
user_prompt = (
|
|
47
|
-
f"Repository context:\n{repo_context}\n\n"
|
|
48
|
-
f"Document type: {doc_config.type}\n"
|
|
49
|
-
f"Update the '{section_name}' section with accurate, detailed information."
|
|
50
|
-
)
|
|
51
|
-
updated_content = await self.provider.complete(
|
|
52
|
-
system=SYSTEM_PROMPT, user=user_prompt
|
|
53
|
-
)
|
|
54
|
-
document = self.renderer.patch_section(
|
|
55
|
-
document, section_name, updated_content + "\n"
|
|
56
|
-
)
|
|
57
|
-
target.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
-
target.write_text(document)
|
|
59
|
-
changed_files.append(target)
|
|
60
|
-
if changed_files:
|
|
61
|
-
self.output.apply(changed_files, "docs: generate initial documentation")
|
|
62
|
-
Config.mark_initialized(self.repo_root)
|
|
63
|
-
self.register_in_registry()
|
|
64
|
-
|
|
65
|
-
async def run(self, diff_text: str) -> bool:
|
|
66
|
-
if not Config.is_initialized(self.repo_root):
|
|
67
|
-
await self.init()
|
|
68
|
-
return False
|
|
69
|
-
triggers = self.config.triggers
|
|
70
|
-
analyzer = DiffAnalyzer(
|
|
71
|
-
diff_text=diff_text,
|
|
72
|
-
trigger_paths=triggers.paths if triggers else [],
|
|
73
|
-
ignore_paths=triggers.ignore if triggers else [],
|
|
74
|
-
)
|
|
75
|
-
if not analyzer.has_relevant_changes():
|
|
76
|
-
return True
|
|
77
|
-
changed_files: list[Path] = []
|
|
78
|
-
diff_summary = analyzer.diff_summary()
|
|
79
|
-
for doc_config in self.config.documents:
|
|
80
|
-
target = self.repo_root / doc_config.target
|
|
81
|
-
if not target.exists():
|
|
82
|
-
continue
|
|
83
|
-
document = target.read_text()
|
|
84
|
-
section_names = self.renderer.auto_section_names(document)
|
|
85
|
-
updated = False
|
|
86
|
-
for section_name in section_names:
|
|
87
|
-
user_prompt = (
|
|
88
|
-
f"Diff summary:\n{diff_summary}\n\n"
|
|
89
|
-
f"Current document:\n{document}\n\n"
|
|
90
|
-
f"Update the '{section_name}' section if the diff affects it. "
|
|
91
|
-
f"If no update is needed, return the current section content unchanged."
|
|
92
|
-
)
|
|
93
|
-
updated_content = await self.provider.complete(
|
|
94
|
-
system=SYSTEM_PROMPT, user=user_prompt
|
|
95
|
-
)
|
|
96
|
-
new_document = self.renderer.patch_section(
|
|
97
|
-
document, section_name, updated_content + "\n"
|
|
98
|
-
)
|
|
99
|
-
if new_document != document:
|
|
100
|
-
document = new_document
|
|
101
|
-
updated = True
|
|
102
|
-
if updated:
|
|
103
|
-
target.write_text(document)
|
|
104
|
-
changed_files.append(target)
|
|
105
|
-
if changed_files:
|
|
106
|
-
self.output.apply(changed_files, "docs: update documentation")
|
|
107
|
-
return False
|
|
108
|
-
|
|
109
|
-
async def sync(self) -> None:
|
|
110
|
-
for doc_config in self.config.documents:
|
|
111
|
-
target = self.repo_root / doc_config.target
|
|
112
|
-
template_text = self.loader.load(doc_config.template)
|
|
113
|
-
document = target.read_text() if target.exists() else template_text
|
|
114
|
-
section_names = self.renderer.auto_section_names(template_text)
|
|
115
|
-
repo_context = self.gather_repo_context()
|
|
116
|
-
for section_name in section_names:
|
|
117
|
-
user_prompt = (
|
|
118
|
-
f"Repository context:\n{repo_context}\n\n"
|
|
119
|
-
f"Current document:\n{document}\n\n"
|
|
120
|
-
f"Re-sync the '{section_name}' section to be accurate and up to date."
|
|
121
|
-
)
|
|
122
|
-
updated_content = await self.provider.complete(
|
|
123
|
-
system=SYSTEM_PROMPT, user=user_prompt
|
|
124
|
-
)
|
|
125
|
-
document = self.renderer.patch_section(
|
|
126
|
-
document, section_name, updated_content + "\n"
|
|
127
|
-
)
|
|
128
|
-
target.parent.mkdir(parents=True, exist_ok=True)
|
|
129
|
-
target.write_text(document)
|
|
130
|
-
|
|
131
|
-
def gather_repo_context(self) -> str:
|
|
132
|
-
lines: list[str] = [f"Repo root: {self.repo_root.name}"]
|
|
133
|
-
for candidate in ["pyproject.toml", "package.json", "composer.json", "go.mod"]:
|
|
134
|
-
path = self.repo_root / candidate
|
|
135
|
-
if path.exists():
|
|
136
|
-
lines.append(f"\n{candidate}:\n{path.read_text()[:2000]}")
|
|
137
|
-
break
|
|
138
|
-
src_dirs = ["app", "src", "lib"]
|
|
139
|
-
for src_dir in src_dirs:
|
|
140
|
-
full = self.repo_root / src_dir
|
|
141
|
-
if full.exists():
|
|
142
|
-
files = [str(p.relative_to(self.repo_root)) for p in full.rglob("*.py")][:20]
|
|
143
|
-
lines.append(f"\nSource files in {src_dir}/: {', '.join(files)}")
|
|
144
|
-
break
|
|
145
|
-
return "\n".join(lines)
|
|
146
|
-
|
|
147
|
-
def register_in_registry(self) -> None:
|
|
148
|
-
registry_path = self.repo_root / self.config.registry.path
|
|
149
|
-
registry = Registry(registry_path)
|
|
150
|
-
try:
|
|
151
|
-
remote = (
|
|
152
|
-
subprocess.check_output(["git", "remote", "get-url", "origin"], cwd=self.repo_root)
|
|
153
|
-
.decode()
|
|
154
|
-
.strip()
|
|
155
|
-
)
|
|
156
|
-
except subprocess.CalledProcessError:
|
|
157
|
-
remote = ""
|
|
158
|
-
registry.register(
|
|
159
|
-
ProjectEntry(
|
|
160
|
-
name=self.repo_root.name,
|
|
161
|
-
path=str(self.repo_root),
|
|
162
|
-
remote=remote,
|
|
163
|
-
documents=[DocumentEntry(target=d.target) for d in self.config.documents],
|
|
164
|
-
)
|
|
165
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docwright-0.1.3 → docwright-0.1.5}/docwright/built_in_templates/wiki/development-guide.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|