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.
Files changed (62) hide show
  1. backend/__init__.py +0 -0
  2. backend/app.py +95 -0
  3. backend/desktop_server.py +46 -0
  4. backend/models.py +629 -0
  5. backend/prompts/__init__.py +0 -0
  6. backend/prompts/annotate.py +44 -0
  7. backend/prompts/edit.py +62 -0
  8. backend/prompts/overview.py +57 -0
  9. backend/prompts/qa.py +45 -0
  10. backend/routers/__init__.py +0 -0
  11. backend/routers/analyze.py +271 -0
  12. backend/routers/project.py +872 -0
  13. backend/services/__init__.py +0 -0
  14. backend/services/action_plan.py +366 -0
  15. backend/services/activity.py +313 -0
  16. backend/services/apimap.py +391 -0
  17. backend/services/async_surface.py +123 -0
  18. backend/services/cache.py +183 -0
  19. backend/services/churn.py +244 -0
  20. backend/services/ci_checks.py +317 -0
  21. backend/services/codemap_export.py +136 -0
  22. backend/services/commands.py +276 -0
  23. backend/services/complexity.py +111 -0
  24. backend/services/config_files.py +190 -0
  25. backend/services/contributing.py +308 -0
  26. backend/services/coverage.py +283 -0
  27. backend/services/datamodels.py +190 -0
  28. backend/services/deep_nesting.py +110 -0
  29. backend/services/dependencies.py +270 -0
  30. backend/services/docs.py +255 -0
  31. backend/services/docstrings.py +163 -0
  32. backend/services/duplicate_code.py +165 -0
  33. backend/services/entrypoints.py +152 -0
  34. backend/services/envscan.py +167 -0
  35. backend/services/error_handling.py +239 -0
  36. backend/services/filenames.py +261 -0
  37. backend/services/github_clone.py +129 -0
  38. backend/services/glossary.py +96 -0
  39. backend/services/health_score.py +381 -0
  40. backend/services/importgraph.py +986 -0
  41. backend/services/integrations.py +263 -0
  42. backend/services/licenses.py +505 -0
  43. backend/services/llm.py +117 -0
  44. backend/services/long_functions.py +101 -0
  45. backend/services/ownership.py +194 -0
  46. backend/services/package_purposes.py +112 -0
  47. backend/services/pr_reading.py +181 -0
  48. backend/services/release_map.py +402 -0
  49. backend/services/report_export.py +232 -0
  50. backend/services/risk.py +88 -0
  51. backend/services/scanner.py +546 -0
  52. backend/services/schedules.py +369 -0
  53. backend/services/security.py +445 -0
  54. backend/services/settings_map.py +234 -0
  55. backend/services/symbols.py +605 -0
  56. backend/services/techdebt.py +124 -0
  57. backend/services/too_many_params.py +102 -0
  58. backend/services/typing_coverage.py +184 -0
  59. codeabc-0.1.0.dist-info/METADATA +283 -0
  60. codeabc-0.1.0.dist-info/RECORD +62 -0
  61. codeabc-0.1.0.dist-info/WHEEL +4 -0
  62. 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 开始的行号。确保覆盖文件中所有有意义的代码行。"""