codeabc 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.
- backend/__init__.py +0 -0
- backend/app.py +95 -0
- backend/desktop_server.py +46 -0
- backend/models.py +629 -0
- backend/prompts/__init__.py +0 -0
- backend/prompts/annotate.py +44 -0
- backend/prompts/edit.py +62 -0
- backend/prompts/overview.py +57 -0
- backend/prompts/qa.py +45 -0
- backend/routers/__init__.py +0 -0
- backend/routers/analyze.py +271 -0
- backend/routers/project.py +872 -0
- backend/services/__init__.py +0 -0
- backend/services/action_plan.py +366 -0
- backend/services/activity.py +313 -0
- backend/services/apimap.py +391 -0
- backend/services/async_surface.py +123 -0
- backend/services/cache.py +183 -0
- backend/services/churn.py +244 -0
- backend/services/ci_checks.py +317 -0
- backend/services/codemap_export.py +136 -0
- backend/services/commands.py +276 -0
- backend/services/complexity.py +111 -0
- backend/services/config_files.py +190 -0
- backend/services/contributing.py +308 -0
- backend/services/coverage.py +283 -0
- backend/services/datamodels.py +190 -0
- backend/services/deep_nesting.py +110 -0
- backend/services/dependencies.py +270 -0
- backend/services/docs.py +255 -0
- backend/services/docstrings.py +163 -0
- backend/services/duplicate_code.py +165 -0
- backend/services/entrypoints.py +152 -0
- backend/services/envscan.py +167 -0
- backend/services/error_handling.py +239 -0
- backend/services/filenames.py +261 -0
- backend/services/github_clone.py +129 -0
- backend/services/glossary.py +96 -0
- backend/services/health_score.py +381 -0
- backend/services/importgraph.py +986 -0
- backend/services/integrations.py +263 -0
- backend/services/licenses.py +505 -0
- backend/services/llm.py +117 -0
- backend/services/long_functions.py +101 -0
- backend/services/ownership.py +194 -0
- backend/services/package_purposes.py +112 -0
- backend/services/pr_reading.py +181 -0
- backend/services/release_map.py +402 -0
- backend/services/report_export.py +232 -0
- backend/services/risk.py +88 -0
- backend/services/scanner.py +546 -0
- backend/services/schedules.py +369 -0
- backend/services/security.py +445 -0
- backend/services/settings_map.py +234 -0
- backend/services/symbols.py +605 -0
- backend/services/techdebt.py +124 -0
- backend/services/too_many_params.py +102 -0
- backend/services/typing_coverage.py +184 -0
- codeabc-0.1.0.dist-info/METADATA +283 -0
- codeabc-0.1.0.dist-info/RECORD +62 -0
- codeabc-0.1.0.dist-info/WHEEL +4 -0
- codeabc-0.1.0.dist-info/licenses/LICENSE +21 -0
backend/models.py
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
"""Pydantic models for CodeABC API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FileInfo(BaseModel):
|
|
9
|
+
path: str
|
|
10
|
+
size: int
|
|
11
|
+
language: str = "unknown"
|
|
12
|
+
preview: str = "" # first N lines
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ReadingStep(BaseModel):
|
|
16
|
+
order: int
|
|
17
|
+
path: str
|
|
18
|
+
reason: str
|
|
19
|
+
# Plain-language category of the file (e.g. "数据结构", "测试代码"), inferred
|
|
20
|
+
# from its name so a non-coder can tell entries apart at a glance. None when
|
|
21
|
+
# the filename follows no known convention.
|
|
22
|
+
kind: str | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Hotspot(BaseModel):
|
|
26
|
+
path: str
|
|
27
|
+
language: str = "unknown"
|
|
28
|
+
fan_in: int
|
|
29
|
+
dependents: list[str] = []
|
|
30
|
+
reason: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CodeWalkStep(BaseModel):
|
|
34
|
+
step: int
|
|
35
|
+
path: str
|
|
36
|
+
language: str = "unknown"
|
|
37
|
+
role: str
|
|
38
|
+
reason: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ImportCycle(BaseModel):
|
|
42
|
+
files: list[str]
|
|
43
|
+
size: int
|
|
44
|
+
reason: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class OrphanModule(BaseModel):
|
|
48
|
+
path: str
|
|
49
|
+
language: str = "unknown"
|
|
50
|
+
reason: str
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CouplingHotspot(BaseModel):
|
|
54
|
+
path: str
|
|
55
|
+
language: str = "unknown"
|
|
56
|
+
fan_out: int
|
|
57
|
+
dependencies: list[str] = []
|
|
58
|
+
reason: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BlastRadiusHotspot(BaseModel):
|
|
62
|
+
path: str
|
|
63
|
+
language: str = "unknown"
|
|
64
|
+
blast_radius: int
|
|
65
|
+
direct_dependents: list[str] = []
|
|
66
|
+
reason: str
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ArchitectureLayer(BaseModel):
|
|
70
|
+
path: str
|
|
71
|
+
language: str = "unknown"
|
|
72
|
+
layer: int
|
|
73
|
+
reason: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PackageDependency(BaseModel):
|
|
77
|
+
package: str
|
|
78
|
+
depends_on: list[str] = []
|
|
79
|
+
depended_on_by: list[str] = []
|
|
80
|
+
fan_out: int
|
|
81
|
+
fan_in: int
|
|
82
|
+
reason: str
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ProjectHealth(BaseModel):
|
|
86
|
+
total_code_files: int = 0
|
|
87
|
+
total_directories: int = 0
|
|
88
|
+
circular_dependency_groups: int = 0
|
|
89
|
+
orphan_files: int = 0
|
|
90
|
+
most_depended_on: str = ""
|
|
91
|
+
most_depended_on_fan_in: int = 0
|
|
92
|
+
widest_blast_radius_file: str = ""
|
|
93
|
+
widest_blast_radius: int = 0
|
|
94
|
+
notes: list[str] = []
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class ChurnHotspot(BaseModel):
|
|
98
|
+
path: str
|
|
99
|
+
commits: int
|
|
100
|
+
lines_changed: int = 0
|
|
101
|
+
authors: int = 1
|
|
102
|
+
reason: str
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class CoChangeCoupling(BaseModel):
|
|
106
|
+
file_a: str
|
|
107
|
+
file_b: str
|
|
108
|
+
co_changes: int
|
|
109
|
+
coupling: int # percentage of the rarer file's commits that touch both
|
|
110
|
+
reason: str
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class RiskHotspot(BaseModel):
|
|
114
|
+
path: str
|
|
115
|
+
fan_in: int
|
|
116
|
+
commits: int
|
|
117
|
+
score: int # 0-100, high when a file is both central and frequently changed
|
|
118
|
+
reason: str
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestCoverageFile(BaseModel):
|
|
122
|
+
path: str
|
|
123
|
+
language: str = "unknown"
|
|
124
|
+
fan_in: int = 0 # how many files depend on this untested file
|
|
125
|
+
reason: str
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TestCoverageSummary(BaseModel):
|
|
129
|
+
total_source_files: int = 0
|
|
130
|
+
tested_files: int = 0
|
|
131
|
+
untested_files: int = 0
|
|
132
|
+
test_files: int = 0
|
|
133
|
+
coverage_percent: int = 0 # 0-100, tested source files / total source files
|
|
134
|
+
test_frameworks: list[str] = [] # detected runners, e.g. ["pytest"] / ["vitest"]
|
|
135
|
+
run_command: str | None = None # how to run the tests, e.g. "pytest" / "npm test"
|
|
136
|
+
untested_core: list[TestCoverageFile] = [] # untested, ranked by fan-in
|
|
137
|
+
notes: list[str] = []
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class KnowledgeSilo(BaseModel):
|
|
141
|
+
path: str
|
|
142
|
+
primary_author: str
|
|
143
|
+
ownership: int # percent of the file's commits owned by the primary author
|
|
144
|
+
commits: int
|
|
145
|
+
bus_factor: int = 1 # 1 means a single person holds the majority of the history
|
|
146
|
+
reason: str = ""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TechDebtFile(BaseModel):
|
|
150
|
+
path: str
|
|
151
|
+
count: int # number of TODO/FIXME/HACK/XXX markers in the file
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class DocCoverageFile(BaseModel):
|
|
155
|
+
path: str
|
|
156
|
+
code_lines: int
|
|
157
|
+
comment_lines: int
|
|
158
|
+
ratio: int # 0-100, comment/(code+comment) as a percentage
|
|
159
|
+
reason: str
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class DocCoverage(BaseModel):
|
|
163
|
+
total_source_files: int = 0
|
|
164
|
+
documented_files: int = 0
|
|
165
|
+
undocumented_files: int = 0
|
|
166
|
+
doc_percent: int = 0 # 0-100, documented source files / total source files
|
|
167
|
+
under_documented: list[DocCoverageFile] = [] # ranked by code size
|
|
168
|
+
notes: list[str] = []
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class SilentFailure(BaseModel):
|
|
172
|
+
path: str
|
|
173
|
+
line: int
|
|
174
|
+
category: str # "bare_except" | "swallowed" | "empty_catch"
|
|
175
|
+
snippet: str
|
|
176
|
+
reason: str # plain-language explanation for a non-programmer
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class ErrorHandling(BaseModel):
|
|
180
|
+
total: int = 0
|
|
181
|
+
files_affected: int = 0
|
|
182
|
+
findings: list[SilentFailure] = [] # ranked worst-first
|
|
183
|
+
notes: list[str] = []
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ExternalService(BaseModel):
|
|
187
|
+
name: str
|
|
188
|
+
category: str # AI / 大模型 | 云服务 | 数据库 | 支付 | 通讯 | ...
|
|
189
|
+
note: str # plain-language "what you need to run this"
|
|
190
|
+
file_count: int = 1
|
|
191
|
+
example: str = "" # a sample import/usage line
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Integrations(BaseModel):
|
|
195
|
+
total: int = 0
|
|
196
|
+
services: list[ExternalService] = [] # most-used first
|
|
197
|
+
categories: dict[str, int] = {}
|
|
198
|
+
notes: list[str] = []
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class EnvVar(BaseModel):
|
|
202
|
+
name: str
|
|
203
|
+
required: bool # read via os.environ["X"] somewhere, so it must be set
|
|
204
|
+
count: int = 1 # how many places read it
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class EntryPoint(BaseModel):
|
|
208
|
+
path: str
|
|
209
|
+
kind: str # "command" (declared CLI) | "script" (__main__ guard) | "convention"
|
|
210
|
+
command: str # how to invoke it (e.g. "python run.py" or a console command name)
|
|
211
|
+
reason: str = ""
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class CliCommand(BaseModel):
|
|
215
|
+
name: str
|
|
216
|
+
framework: str # "click" | "typer" | "argparse"
|
|
217
|
+
help: str = "" # one-line description, from help= or the function docstring
|
|
218
|
+
options: list[str] = [] # declared --flags / argument names
|
|
219
|
+
path: str
|
|
220
|
+
line: int
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class DataModelField(BaseModel):
|
|
224
|
+
name: str
|
|
225
|
+
type: str = "" # the annotation as written, e.g. "int" or "list[str]"
|
|
226
|
+
has_default: bool = False
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class DataModel(BaseModel):
|
|
230
|
+
name: str
|
|
231
|
+
kind: str # "dataclass" | "pydantic" | "typeddict" | "namedtuple"
|
|
232
|
+
fields: list[DataModelField] = []
|
|
233
|
+
path: str
|
|
234
|
+
line: int
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class TunableSetting(BaseModel):
|
|
238
|
+
name: str # the constant's UPPER_SNAKE name
|
|
239
|
+
kind: str # "number" | "text" | "flag" | "list" | "mapping" | "other"
|
|
240
|
+
value: str # the literal value, rendered short (strings quoted, long ones truncated)
|
|
241
|
+
path: str
|
|
242
|
+
line: int
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class ConfigFile(BaseModel):
|
|
246
|
+
path: str
|
|
247
|
+
kind: str # "yaml" | "toml" | "ini" | "json" | "properties"
|
|
248
|
+
sections: list[str] = [] # [section] headers (TOML/INI)
|
|
249
|
+
keys: list[str] = [] # top-level settings the reader can change
|
|
250
|
+
setting_count: int = 0
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class ScheduledTask(BaseModel):
|
|
254
|
+
name: str # what runs (function / job name, or the workflow file stem)
|
|
255
|
+
# "github-actions" | "apscheduler" | "celery" | "schedule" | "repeat-every"
|
|
256
|
+
# | "node-cron" | "nestjs" | "interval"
|
|
257
|
+
mechanism: str
|
|
258
|
+
schedule: str = "" # the raw schedule as written ("*/5 * * * *", "seconds=30")
|
|
259
|
+
schedule_human: str = "" # plain-language gloss, "" when not derivable
|
|
260
|
+
path: str
|
|
261
|
+
line: int
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class CiCheck(BaseModel):
|
|
265
|
+
tool: str # the tool that runs the gate ("ruff", "pytest", "tsc", ...)
|
|
266
|
+
# "lint" | "format" | "typecheck" | "test" | "coverage" | "security"
|
|
267
|
+
# | "build" | "deploy"
|
|
268
|
+
category: str
|
|
269
|
+
# "github-actions" | "pre-commit" | "gitlab-ci" | "circleci"
|
|
270
|
+
# | "azure-pipelines" | "travis" | "jenkins"
|
|
271
|
+
system: str
|
|
272
|
+
trigger: str = "" # plain-language trigger gloss (GitHub Actions only)
|
|
273
|
+
path: str
|
|
274
|
+
line: int
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class LicenseFinding(BaseModel):
|
|
278
|
+
spdx: str # canonical SPDX id ("MIT", "GPL-3.0", ...) or "unknown"
|
|
279
|
+
name: str # English display name
|
|
280
|
+
name_zh: str # plain-Chinese name
|
|
281
|
+
# "permissive" | "weak-copyleft" | "strong-copyleft" | "network-copyleft"
|
|
282
|
+
# | "public-domain" | "source-available" | "unknown"
|
|
283
|
+
category: str
|
|
284
|
+
source_path: str # the file the license was found in
|
|
285
|
+
# "license-file" | "manifest" | "classifier" | "spdx-tag"
|
|
286
|
+
source_kind: str
|
|
287
|
+
line: int
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class LicenseSummary(BaseModel):
|
|
291
|
+
total: int = 0 # distinct recognised licenses
|
|
292
|
+
primary: str = "" # best-guess project license SPDX id, "" if none found
|
|
293
|
+
primary_category: str = ""
|
|
294
|
+
found: list[LicenseFinding] = []
|
|
295
|
+
categories: list[str] = []
|
|
296
|
+
notes: list[str] = []
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class ReleaseAutomation(BaseModel):
|
|
300
|
+
trigger: str = "" # "tag-push" | "release" | "manual" | ""
|
|
301
|
+
trigger_zh: str = "" # plain-language gloss of what kicks the release off
|
|
302
|
+
# "PyPI" | "npm" | "GitHub Release" | "crates.io" | "RubyGems" | "Docker 镜像"
|
|
303
|
+
target: str
|
|
304
|
+
path: str
|
|
305
|
+
line: int
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class ReleaseSummary(BaseModel):
|
|
309
|
+
version: str = "" # current version string, "" if none found
|
|
310
|
+
version_source: str = "" # path the version was read from
|
|
311
|
+
# "pyproject" | "package-json" | "cargo" | "setup-py" | "setup-cfg"
|
|
312
|
+
# | "version-file" | "dunder" | "pom" | "gradle"
|
|
313
|
+
version_source_kind: str = ""
|
|
314
|
+
dynamic_from_vcs: bool = False # version derived from git tags at build time
|
|
315
|
+
# "semver" | "zerover" | "calver" | "prerelease" | "twopart" | "single" | "other"
|
|
316
|
+
scheme: str = ""
|
|
317
|
+
scheme_zh: str = ""
|
|
318
|
+
changelog_path: str = ""
|
|
319
|
+
changelog_style: str = "none" # "keepachangelog" | "versioned" | "freeform" | "none"
|
|
320
|
+
changelog_style_zh: str = ""
|
|
321
|
+
automation: list[ReleaseAutomation] = []
|
|
322
|
+
publish_targets: list[str] = []
|
|
323
|
+
notes: list[str] = []
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class ContributionRequirement(BaseModel):
|
|
327
|
+
# "guide" | "pr-template" | "issue-template" | "commit-convention" | "dco"
|
|
328
|
+
# | "cla" | "codeowners" | "code-of-conduct" | "security"
|
|
329
|
+
kind: str
|
|
330
|
+
label_zh: str # plain-Chinese label ("DCO 签署", "提交信息规范", ...)
|
|
331
|
+
detail_zh: str # one-line gloss a newcomer can act on
|
|
332
|
+
path: str # the community-health file this requirement was read from
|
|
333
|
+
line: int = 0 # line of the matched text, 0 for a plain presence signal
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class ContributionGuide(BaseModel):
|
|
337
|
+
has_guide: bool = False # a CONTRIBUTING file exists
|
|
338
|
+
requirements: list[ContributionRequirement] = []
|
|
339
|
+
notes: list[str] = []
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class ComplexFile(BaseModel):
|
|
343
|
+
path: str
|
|
344
|
+
complexity: int # approximate cyclomatic complexity (decision points + 1)
|
|
345
|
+
functions: int = 0
|
|
346
|
+
reason: str = ""
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class Dependency(BaseModel):
|
|
350
|
+
name: str
|
|
351
|
+
version: str = "" # the declared version constraint, if any
|
|
352
|
+
kind: str # "runtime" | "dev" | "optional"
|
|
353
|
+
manifest: str # the manifest file it was declared in (requirements.txt, etc.)
|
|
354
|
+
# Plain-language note on what this library is for; None when the package is
|
|
355
|
+
# not in the curated dictionary.
|
|
356
|
+
purpose: str | None = None
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class ActivityWindow(BaseModel):
|
|
360
|
+
commits: int = 0
|
|
361
|
+
authors: list[str] = []
|
|
362
|
+
files: list[str] = []
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class ActivityContributor(BaseModel):
|
|
366
|
+
author: str
|
|
367
|
+
commits: int
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class ActivitySummary(BaseModel):
|
|
371
|
+
available: bool = False
|
|
372
|
+
total_commits: int = 0
|
|
373
|
+
first_commit_days_ago: float | None = None
|
|
374
|
+
last_commit_days_ago: float | None = None
|
|
375
|
+
label: str = "" # active | slowing | quiet | stale | abandoned | unknown
|
|
376
|
+
label_zh: str = ""
|
|
377
|
+
windows: dict[str, ActivityWindow] = {}
|
|
378
|
+
top_contributors: list[ActivityContributor] = []
|
|
379
|
+
recently_changed: list[str] = []
|
|
380
|
+
notes: list[str] = []
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class HealthScore(BaseModel):
|
|
384
|
+
score: int = 0
|
|
385
|
+
grade: str = "F"
|
|
386
|
+
category_scores: dict[str, int] = {}
|
|
387
|
+
weights: dict[str, float] = {}
|
|
388
|
+
strengths: list[str] = []
|
|
389
|
+
weaknesses: list[str] = []
|
|
390
|
+
notes: list[str] = []
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class ActionItem(BaseModel):
|
|
394
|
+
priority: str # "high" | "medium" | "low"
|
|
395
|
+
category: str # security | test_coverage | complexity | architecture | tech_debt
|
|
396
|
+
title: str
|
|
397
|
+
target: str = "" # file path, or "" when not file-specific
|
|
398
|
+
detail: str = ""
|
|
399
|
+
effort: str = "medium" # "small" | "medium" | "large"
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class ActionPlan(BaseModel):
|
|
403
|
+
total: int = 0
|
|
404
|
+
items: list[ActionItem] = []
|
|
405
|
+
notes: list[str] = []
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
class ApiRoute(BaseModel):
|
|
409
|
+
method: str # GET | POST | PUT | PATCH | DELETE | OPTIONS | HEAD | ANY | ALL
|
|
410
|
+
path: str # the route path as declared in the source
|
|
411
|
+
handler: str = "" # handler function / class name
|
|
412
|
+
description: str = "" # brief description from docstring or annotation
|
|
413
|
+
file: str
|
|
414
|
+
line: int
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
class ApiMap(BaseModel):
|
|
418
|
+
total: int = 0
|
|
419
|
+
routes: list[ApiRoute] = []
|
|
420
|
+
frameworks: list[str] = [] # detected framework names
|
|
421
|
+
notes: list[str] = []
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class SecurityFinding(BaseModel):
|
|
425
|
+
file: str
|
|
426
|
+
line: int
|
|
427
|
+
category: str # "hardcoded_secret" | "dangerous_call" | "shell_injection" | "debug_mode"
|
|
428
|
+
snippet: str # the matching line, truncated
|
|
429
|
+
reason: str # plain-language explanation for a non-programmer
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class SecuritySummary(BaseModel):
|
|
433
|
+
total: int = 0
|
|
434
|
+
critical: int = 0 # hardcoded_secret + dangerous_call count (non-test files)
|
|
435
|
+
findings: list[SecurityFinding] = []
|
|
436
|
+
notes: list[str] = []
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class ProjectSummary(BaseModel):
|
|
440
|
+
"""A lightweight entry in the project library list (no file contents)."""
|
|
441
|
+
|
|
442
|
+
id: str
|
|
443
|
+
name: str
|
|
444
|
+
total_files: int = 0
|
|
445
|
+
created_at: float | None = None # unix seconds; None for in-memory-only
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class ProjectMeta(BaseModel):
|
|
449
|
+
id: str
|
|
450
|
+
name: str
|
|
451
|
+
total_files: int
|
|
452
|
+
files: list[FileInfo]
|
|
453
|
+
reading_map: list[ReadingStep]
|
|
454
|
+
hotspots: list[Hotspot] = []
|
|
455
|
+
code_walk: list[CodeWalkStep] = []
|
|
456
|
+
import_cycles: list[ImportCycle] = []
|
|
457
|
+
orphan_modules: list[OrphanModule] = []
|
|
458
|
+
coupling_hotspots: list[CouplingHotspot] = []
|
|
459
|
+
blast_radius: list[BlastRadiusHotspot] = []
|
|
460
|
+
architecture_layers: list[ArchitectureLayer] = []
|
|
461
|
+
package_dependencies: list[PackageDependency] = []
|
|
462
|
+
health: ProjectHealth | None = None
|
|
463
|
+
churn_hotspots: list[ChurnHotspot] = []
|
|
464
|
+
co_change_couplings: list[CoChangeCoupling] = []
|
|
465
|
+
risk_hotspots: list[RiskHotspot] = []
|
|
466
|
+
test_coverage: TestCoverageSummary | None = None
|
|
467
|
+
knowledge_silos: list[KnowledgeSilo] = []
|
|
468
|
+
tech_debt_files: list[TechDebtFile] = []
|
|
469
|
+
env_vars: list[EnvVar] = []
|
|
470
|
+
entry_points: list[EntryPoint] = []
|
|
471
|
+
cli_commands: list[CliCommand] = []
|
|
472
|
+
data_models: list[DataModel] = []
|
|
473
|
+
tunable_settings: list[TunableSetting] = []
|
|
474
|
+
config_files: list[ConfigFile] = []
|
|
475
|
+
scheduled_tasks: list[ScheduledTask] = []
|
|
476
|
+
ci_checks: list[CiCheck] = []
|
|
477
|
+
licenses: LicenseSummary | None = None
|
|
478
|
+
complexity_files: list[ComplexFile] = []
|
|
479
|
+
dependencies: list[Dependency] = []
|
|
480
|
+
security: SecuritySummary | None = None
|
|
481
|
+
api_map: ApiMap | None = None
|
|
482
|
+
activity: ActivitySummary | None = None
|
|
483
|
+
health_score: HealthScore | None = None
|
|
484
|
+
action_plan: ActionPlan | None = None
|
|
485
|
+
doc_coverage: DocCoverage | None = None
|
|
486
|
+
error_handling: ErrorHandling | None = None
|
|
487
|
+
integrations: Integrations | None = None
|
|
488
|
+
release: ReleaseSummary | None = None
|
|
489
|
+
contribution: ContributionGuide | None = None
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class FileRole(BaseModel):
|
|
493
|
+
path: str
|
|
494
|
+
role: str
|
|
495
|
+
importance: str = "medium"
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class ProjectOverview(BaseModel):
|
|
499
|
+
summary: str = ""
|
|
500
|
+
description: str = ""
|
|
501
|
+
files: list[FileRole] = []
|
|
502
|
+
how_to_run: list[str] = []
|
|
503
|
+
quick_tips: list[str] = []
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
class Annotation(BaseModel):
|
|
507
|
+
line_start: int
|
|
508
|
+
line_end: int
|
|
509
|
+
annotation: str
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class FileAnnotations(BaseModel):
|
|
513
|
+
path: str
|
|
514
|
+
language: str
|
|
515
|
+
annotations: list[Annotation]
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
class QARequest(BaseModel):
|
|
519
|
+
question: str
|
|
520
|
+
code: str = ""
|
|
521
|
+
file_path: str = ""
|
|
522
|
+
language: str = ""
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
class EditRequest(BaseModel):
|
|
526
|
+
instruction: str
|
|
527
|
+
code: str
|
|
528
|
+
file_path: str = ""
|
|
529
|
+
language: str = ""
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
class GlossaryTerm(BaseModel):
|
|
533
|
+
term: str
|
|
534
|
+
definition: str
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
class FileGlossary(BaseModel):
|
|
538
|
+
path: str
|
|
539
|
+
terms: list[GlossaryTerm] = []
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
class FileDependencies(BaseModel):
|
|
543
|
+
path: str
|
|
544
|
+
imports: list[str] = [] # in-project files this file imports
|
|
545
|
+
imported_by: list[str] = [] # in-project files that import this file
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class Definition(BaseModel):
|
|
549
|
+
name: str
|
|
550
|
+
qualname: str = "" # "Class.method" for methods, plain name otherwise
|
|
551
|
+
kind: str # function | class | method
|
|
552
|
+
parent: str | None = None # enclosing class for a method
|
|
553
|
+
lang: str = "" # python | js
|
|
554
|
+
file: str
|
|
555
|
+
line: int
|
|
556
|
+
exported: bool = False # part of the public surface (non-underscore / exported)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
class DefinitionMatches(BaseModel):
|
|
560
|
+
"""Where a clicked name is defined — the jump-to-definition payload."""
|
|
561
|
+
|
|
562
|
+
name: str # the queried name
|
|
563
|
+
total: int = 0 # number of places that define it
|
|
564
|
+
definitions: list[Definition] = []
|
|
565
|
+
notes: list[str] = []
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class PublicApi(BaseModel):
|
|
569
|
+
"""A project's public surface — the names it exposes for other code to call."""
|
|
570
|
+
|
|
571
|
+
total: int = 0 # number of public definitions
|
|
572
|
+
definitions: list[Definition] = []
|
|
573
|
+
notes: list[str] = []
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
class Reference(BaseModel):
|
|
577
|
+
name: str
|
|
578
|
+
file: str
|
|
579
|
+
line: int
|
|
580
|
+
text: str = "" # the trimmed source line, for a one-line preview
|
|
581
|
+
is_definition: bool = False # True when this occurrence is the declaration
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
class ReferenceMatches(BaseModel):
|
|
585
|
+
"""Where a clicked name is used — the find-all-references payload."""
|
|
586
|
+
|
|
587
|
+
name: str # the queried name
|
|
588
|
+
total: int = 0 # number of use sites (including the definition)
|
|
589
|
+
files: int = 0 # number of distinct files it appears in
|
|
590
|
+
references: list[Reference] = []
|
|
591
|
+
notes: list[str] = []
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
class OutlineNode(BaseModel):
|
|
595
|
+
name: str
|
|
596
|
+
qualname: str = "" # "Class.method" for methods, plain name otherwise
|
|
597
|
+
kind: str # function | class | method
|
|
598
|
+
parent: str | None = None # enclosing class for a method
|
|
599
|
+
lang: str = "" # python | js
|
|
600
|
+
file: str
|
|
601
|
+
line: int
|
|
602
|
+
exported: bool = False # part of the public surface (non-underscore / exported)
|
|
603
|
+
children: list["OutlineNode"] = [] # a class's methods (and nested classes)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class FileOutline(BaseModel):
|
|
607
|
+
"""One file's structure, in source order — the file's table of contents."""
|
|
608
|
+
|
|
609
|
+
file: str
|
|
610
|
+
lang: str = "" # python | js
|
|
611
|
+
total: int = 0 # number of definitions, nested ones included
|
|
612
|
+
outline: list[OutlineNode] = []
|
|
613
|
+
notes: list[str] = []
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
class GitHubRequest(BaseModel):
|
|
617
|
+
url: str = Field(..., pattern=r"^https?://github\.com/.+/.+")
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
class UploadedFile(BaseModel):
|
|
621
|
+
path: str
|
|
622
|
+
content: str
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
class AnalyzeRequest(BaseModel):
|
|
626
|
+
"""Sent from frontend with uploaded files."""
|
|
627
|
+
|
|
628
|
+
files: list[UploadedFile]
|
|
629
|
+
project_name: str = "untitled"
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Prompt for generating line-by-line code annotations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def build_annotation_prompt(code: str, language: str = "python") -> str:
|
|
7
|
+
"""Build the LLM prompt for code annotation.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
code: full source code of the file
|
|
11
|
+
language: programming language name
|
|
12
|
+
"""
|
|
13
|
+
# for very long files, only annotate the first 200 lines
|
|
14
|
+
lines = code.splitlines()
|
|
15
|
+
if len(lines) > 200:
|
|
16
|
+
code = "\n".join(lines[:200])
|
|
17
|
+
truncated_note = f"\n\n(注意:文件共 {len(lines)} 行,这里只展示前 200 行)"
|
|
18
|
+
else:
|
|
19
|
+
truncated_note = ""
|
|
20
|
+
|
|
21
|
+
return f"""你是一个耐心的编程导师。下面是一个 {language} 源文件的内容。
|
|
22
|
+
请为这段代码生成逐行/逐块的中文批注,帮助完全不懂编程的人理解每一部分在做什么。
|
|
23
|
+
|
|
24
|
+
要求:
|
|
25
|
+
1. 批注粒度要细:每 1-3 行逻辑代码为一个批注单元,不要把很多行合并成一个笼统的解释
|
|
26
|
+
2. 用大白话解释,不要使用编程术语(比如不要说"变量""函数",而是说"名字""小助手")
|
|
27
|
+
3. 如果代码中有数字常量(比如 0.05、100、3600),解释这个数字代表什么意思
|
|
28
|
+
4. 适当用日常生活的类比让解释更生动
|
|
29
|
+
5. 空行、纯注释行可以跳过不批注
|
|
30
|
+
6. 对于 import 语句,简单说明引入了什么工具、这个工具用来做什么
|
|
31
|
+
|
|
32
|
+
代码内容({language}):
|
|
33
|
+
```{language}
|
|
34
|
+
{code}
|
|
35
|
+
```{truncated_note}
|
|
36
|
+
|
|
37
|
+
严格输出 JSON 数组格式(不要输出其他任何内容):
|
|
38
|
+
[
|
|
39
|
+
{{"line_start": 1, "line_end": 1, "annotation": "这行的中文解释"}},
|
|
40
|
+
{{"line_start": 3, "line_end": 5, "annotation": "这几行的中文解释"}},
|
|
41
|
+
...
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
注意 line_start 和 line_end 是从 1 开始的行号。确保覆盖文件中所有有意义的代码行。"""
|