codetrail-cli 1.4.1__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 (171) hide show
  1. codetrail/__init__.py +3 -0
  2. codetrail/__main__.py +5 -0
  3. codetrail/agents/__init__.py +4 -0
  4. codetrail/agents/convert.py +92 -0
  5. codetrail/agents/export.py +284 -0
  6. codetrail/agents/instructions.py +42 -0
  7. codetrail/agents/protocol.py +111 -0
  8. codetrail/agents/tools.py +185 -0
  9. codetrail/assistant/__init__.py +161 -0
  10. codetrail/assistant/claude_code.py +330 -0
  11. codetrail/assistant/codex.py +260 -0
  12. codetrail/assistant/environment.py +66 -0
  13. codetrail/assistant/estimate.py +225 -0
  14. codetrail/assistant/fake.py +74 -0
  15. codetrail/assistant/guard.py +80 -0
  16. codetrail/assistant/guard_hook.py +58 -0
  17. codetrail/assistant/local.py +245 -0
  18. codetrail/assistant/local_tools.py +177 -0
  19. codetrail/assistant/prompts.py +404 -0
  20. codetrail/assistant/routing.py +86 -0
  21. codetrail/assistant/runner.py +120 -0
  22. codetrail/assistant/status.py +186 -0
  23. codetrail/assistant/usage.py +93 -0
  24. codetrail/bridge.py +292 -0
  25. codetrail/cli.py +407 -0
  26. codetrail/config.py +485 -0
  27. codetrail/database/__init__.py +60 -0
  28. codetrail/database/migrations/0001_facts.sql +34 -0
  29. codetrail/database/migrations/0002_settings.sql +5 -0
  30. codetrail/database/migrations/0003_learning.sql +34 -0
  31. codetrail/database/migrations/0004_assistant_usage.sql +24 -0
  32. codetrail/database/migrations/0005_page_failures.sql +7 -0
  33. codetrail/database/migrations/0006_metric_values.sql +9 -0
  34. codetrail/errors.py +5 -0
  35. codetrail/extract/__init__.py +249 -0
  36. codetrail/extract/adr.py +98 -0
  37. codetrail/extract/github_actions.py +140 -0
  38. codetrail/extract/openapi.py +101 -0
  39. codetrail/extract/python.py +221 -0
  40. codetrail/extract/swift.py +204 -0
  41. codetrail/extract/terraform.py +166 -0
  42. codetrail/extract/typescript.py +464 -0
  43. codetrail/facts/__init__.py +112 -0
  44. codetrail/facts/store.py +209 -0
  45. codetrail/generate/__init__.py +1 -0
  46. codetrail/generate/failures.py +31 -0
  47. codetrail/generate/outline.py +159 -0
  48. codetrail/generate/revise.py +72 -0
  49. codetrail/generate/run.py +530 -0
  50. codetrail/generate/scope.py +135 -0
  51. codetrail/generate/validate.py +218 -0
  52. codetrail/guide/__init__.py +147 -0
  53. codetrail/learn/__init__.py +143 -0
  54. codetrail/learn/routes.py +139 -0
  55. codetrail/locales/ar/LC_MESSAGES/codetrail.mo +0 -0
  56. codetrail/locales/ar/LC_MESSAGES/codetrail.po +2236 -0
  57. codetrail/locales/codetrail.pot +1942 -0
  58. codetrail/lock.py +114 -0
  59. codetrail/metrics/__init__.py +6 -0
  60. codetrail/metrics/activity.py +96 -0
  61. codetrail/metrics/churn.py +156 -0
  62. codetrail/metrics/commits.py +45 -0
  63. codetrail/metrics/coverage.py +114 -0
  64. codetrail/metrics/decisions.py +77 -0
  65. codetrail/metrics/history.py +47 -0
  66. codetrail/metrics/inventory.py +63 -0
  67. codetrail/metrics/rationale.py +74 -0
  68. codetrail/metrics/releases.py +26 -0
  69. codetrail/metrics/report.py +111 -0
  70. codetrail/metrics/repository.py +128 -0
  71. codetrail/metrics/size.py +95 -0
  72. codetrail/py.typed +0 -0
  73. codetrail/remove.py +50 -0
  74. codetrail/repo/__init__.py +4 -0
  75. codetrail/repo/git.py +56 -0
  76. codetrail/repo/gitleaks.toml +6 -0
  77. codetrail/repo/history.py +262 -0
  78. codetrail/repo/mirror.py +137 -0
  79. codetrail/repo/refresh.py +41 -0
  80. codetrail/repo/rules.py +83 -0
  81. codetrail/repo/secrets.py +145 -0
  82. codetrail/repo/signal.py +65 -0
  83. codetrail/repo/source.py +166 -0
  84. codetrail/search.py +178 -0
  85. codetrail/server.py +45 -0
  86. codetrail/system.py +484 -0
  87. codetrail/update.py +276 -0
  88. codetrail/web/__init__.py +1 -0
  89. codetrail/web/app.py +897 -0
  90. codetrail/web/diagrams.py +414 -0
  91. codetrail/web/documentation.py +146 -0
  92. codetrail/web/i18n.py +44 -0
  93. codetrail/web/navigation.py +127 -0
  94. codetrail/web/render.py +126 -0
  95. codetrail/web/repository_view.py +230 -0
  96. codetrail/web/security.py +171 -0
  97. codetrail/web/static/brand/favicon.svg +1 -0
  98. codetrail/web/static/brand/mark.svg +1 -0
  99. codetrail/web/static/css/base.css +192 -0
  100. codetrail/web/static/css/components.css +2254 -0
  101. codetrail/web/static/css/content.css +310 -0
  102. codetrail/web/static/css/layout.css +410 -0
  103. codetrail/web/static/css/print.css +42 -0
  104. codetrail/web/static/css/tokens.css +228 -0
  105. codetrail/web/static/js/api.js +49 -0
  106. codetrail/web/static/js/ask.js +182 -0
  107. codetrail/web/static/js/d2-worker.js +34 -0
  108. codetrail/web/static/js/d2.js +28 -0
  109. codetrail/web/static/js/diagram.js +52 -0
  110. codetrail/web/static/js/documentation.js +24 -0
  111. codetrail/web/static/js/learning.js +47 -0
  112. codetrail/web/static/js/main.js +38 -0
  113. codetrail/web/static/js/outline.js +44 -0
  114. codetrail/web/static/js/palette.js +211 -0
  115. codetrail/web/static/js/repository.js +528 -0
  116. codetrail/web/static/js/resize.js +126 -0
  117. codetrail/web/static/js/shortcuts.js +87 -0
  118. codetrail/web/static/js/sidebar.js +194 -0
  119. codetrail/web/static/js/theme-init.js +27 -0
  120. codetrail/web/static/js/theme.js +42 -0
  121. codetrail/web/static/js/update.js +107 -0
  122. codetrail/web/static/vendor/d2/LICENSE +375 -0
  123. codetrail/web/static/vendor/d2/LICENSE-elk +263 -0
  124. codetrail/web/static/vendor/d2/LICENSE-wasm_exec +27 -0
  125. codetrail/web/static/vendor/d2/VERSION +12 -0
  126. codetrail/web/static/vendor/d2/d2.wasm +0 -0
  127. codetrail/web/static/vendor/d2/elk.js +105914 -0
  128. codetrail/web/static/vendor/d2/wasm_exec.js +656 -0
  129. codetrail/web/static/vendor/inter/InterVariable-Italic.woff2 +0 -0
  130. codetrail/web/static/vendor/inter/InterVariable.woff2 +0 -0
  131. codetrail/web/static/vendor/inter/LICENSE.txt +92 -0
  132. codetrail/web/static/vendor/inter/VERSION +6 -0
  133. codetrail/web/static/vendor/noto-sans-arabic/NotoSansArabic-wght.woff2 +0 -0
  134. codetrail/web/static/vendor/noto-sans-arabic/OFL.txt +93 -0
  135. codetrail/web/static/vendor/noto-sans-arabic/VERSION +12 -0
  136. codetrail/web/target_view.py +63 -0
  137. codetrail/web/templates/answers.html +25 -0
  138. codetrail/web/templates/area.html +46 -0
  139. codetrail/web/templates/base.html +37 -0
  140. codetrail/web/templates/decisions.html +30 -0
  141. codetrail/web/templates/diagram.html +10 -0
  142. codetrail/web/templates/digests.html +24 -0
  143. codetrail/web/templates/documentation.html +247 -0
  144. codetrail/web/templates/error.html +9 -0
  145. codetrail/web/templates/estimate.html +48 -0
  146. codetrail/web/templates/fact.html +29 -0
  147. codetrail/web/templates/home.html +196 -0
  148. codetrail/web/templates/layout/ask_panel.html +27 -0
  149. codetrail/web/templates/layout/header.html +32 -0
  150. codetrail/web/templates/layout/icons.html +27 -0
  151. codetrail/web/templates/layout/outline.html +4 -0
  152. codetrail/web/templates/layout/palette.html +27 -0
  153. codetrail/web/templates/layout/shortcuts.html +27 -0
  154. codetrail/web/templates/layout/sidebar.html +79 -0
  155. codetrail/web/templates/layout/update_dialog.html +18 -0
  156. codetrail/web/templates/macros.html +13 -0
  157. codetrail/web/templates/page.html +93 -0
  158. codetrail/web/templates/progress.html +46 -0
  159. codetrail/web/templates/repository.html +369 -0
  160. codetrail/web/templates/search.html +31 -0
  161. codetrail/web/templates/source.html +15 -0
  162. codetrail/web/templates/system.html +43 -0
  163. codetrail/web/templates/system_arrows.html +17 -0
  164. codetrail/web/templates/system_legend.html +9 -0
  165. codetrail/web/templates/update_log.html +30 -0
  166. codetrail/web/trend.py +22 -0
  167. codetrail_cli-1.4.1.dist-info/METADATA +372 -0
  168. codetrail_cli-1.4.1.dist-info/RECORD +171 -0
  169. codetrail_cli-1.4.1.dist-info/WHEEL +4 -0
  170. codetrail_cli-1.4.1.dist-info/entry_points.txt +3 -0
  171. codetrail_cli-1.4.1.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,369 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}{{ _("Repository") }}{% endblock %}
3
+ {% block content %}
4
+ {% macro capped(items) -%}
5
+ <ul class="list" lang="en" dir="ltr">{% for item in items[:listed] %}<li>{{ caller(item) }}</li>{% endfor %}</ul>
6
+ {% if items|length > listed %}
7
+ <details class="more">
8
+ <summary>{% trans count=items|length - listed %}{{ count }} more{% endtrans %}</summary>
9
+ <ul class="list" lang="en" dir="ltr">{% for item in items[listed:] %}<li>{{ caller(item) }}</li>{% endfor %}</ul>
10
+ </details>
11
+ {% endif %}
12
+ {%- endmacro %}
13
+ {% macro change(tile, points=false) -%}
14
+ {% if tile.change is none %}{{ _("No earlier update to compare with.") }}{% elif points %}{% trans change="%+d"|format(tile.change) %}{{ change }} points since the last update{% endtrans %}{% else %}{% trans change="%+d"|format(tile.change) %}{{ change }} since the last update{% endtrans %}{% endif %}
15
+ {%- endmacro %}
16
+ {% macro trend(tile) %}
17
+ {% if tile.points %}
18
+ {% set count = tile.trend_text.split(", ")|length %}
19
+ <svg class="trend" viewBox="0 0 120 32" role="img" aria-label="{% trans count=count, values=tile.trend_text %}Trend over {{ count }} update: {{ values }}{% pluralize %}Trend over {{ count }} updates: {{ values }}{% endtrans %}"><polyline points="{{ tile.points }}"/></svg>
20
+ {% endif %}
21
+ {% endmacro %}
22
+ <div class="wide">
23
+ <header class="page-head repository-head">
24
+ <div>
25
+ <h1>{{ _("Repository") }}</h1>
26
+ <p class="muted">{{ _("What the repository is: its history, its releases, its code and its facts. Read from the code and its history, without calling your assistant.") }}</p>
27
+ </div>
28
+ {% if report is not none and report.activity and report.activity.days_start %}
29
+ <div class="segmented" role="group" aria-label="{{ _('Time range') }}" data-range-picker hidden>
30
+ <button type="button" data-range="all" aria-pressed="true">{{ _("All time") }}</button>
31
+ <button type="button" data-range="2y" aria-pressed="false">{{ _("2 years") }}</button>
32
+ <button type="button" data-range="1y" aria-pressed="false">{{ _("1 year") }}</button>
33
+ <button type="button" data-range="90d" aria-pressed="false">{{ _("90 days") }}</button>
34
+ </div>
35
+ {% endif %}
36
+ </header>
37
+ {% if report is none %}
38
+ <section class="empty">
39
+ {% if unavailable %}
40
+ <h2>{{ _("The statistics aren't available just now") }}</h2>
41
+ <p>{{ _("Codetrail couldn't read the target's data; the log says why.") }}</p>
42
+ {% else %}
43
+ <h2>{{ _("No facts yet") }}</h2>
44
+ <p>{{ _("Update the guide once: reading the code is free, and the statistics come from it.") }}</p>
45
+ <button type="button" class="button primary" data-update-button>{{ _("Update the guide") }}</button>
46
+ {% endif %}
47
+ </section>
48
+ {% else %}
49
+ {% set activity, releases, size = report.activity, report.releases, report.size %}
50
+ <div class="stats">
51
+ <section class="stat metric" aria-labelledby="tile-age">
52
+ <span class="card-label" id="tile-age">{{ _("Age") }}</span>
53
+ {% if age is none %}
54
+ <span class="stat-value">—</span><span class="stat-note">{{ _("The history couldn't be read just now.") }}</span>
55
+ {% else %}
56
+ {% set years, months = age %}
57
+ <span class="stat-value">{% if not years and not months %}{% trans count=[(today - activity.first).days, 0]|max %}{{ count }} day{% pluralize %}{{ count }} days{% endtrans %}{% else %}{% if years %}{% trans count=years %}{{ count }} year{% pluralize %}{{ count }} years{% endtrans %}{% endif %}{% if years and months %}, {% endif %}{% if months %}{% trans count=months %}{{ count }} month{% pluralize %}{{ count }} months{% endtrans %}{% endif %}{% endif %}</span>
58
+ <span class="stat-note">{% trans date=activity.first.isoformat() %}First commit on <span class="nowrap">{{ date }}</span>{% endtrans %}</span>
59
+ {% endif %}
60
+ </section>
61
+ {% set tile = tiles["commits"] %}
62
+ <section class="stat metric" aria-labelledby="tile-commits">
63
+ <span class="card-label" id="tile-commits">{{ _("Commits") }}</span>
64
+ {% if tile.value is none %}
65
+ <span class="stat-value">—</span><span class="stat-note">{{ _("The history couldn't be read just now.") }}</span>
66
+ {% else %}
67
+ <span class="stat-value">{{ tile.value.numerator|number }}</span>
68
+ <span class="stat-note">{% trans count=activity.merges %}{{ count }} merge{% pluralize %}{{ count }} merges{% endtrans %}</span>
69
+ <span class="stat-note">{{ change(tile) }}</span>
70
+ {{ trend(tile) }}
71
+ {% endif %}
72
+ </section>
73
+ <section class="stat metric" aria-labelledby="tile-releases">
74
+ <span class="card-label" id="tile-releases">{{ _("Releases") }}</span>
75
+ {% if releases is none %}
76
+ <span class="stat-value">—</span><span class="stat-note">{{ _("Not available just now.") }}</span>
77
+ {% elif not releases.tags %}
78
+ <span class="stat-value">0</span><span class="stat-note">{{ _("No tags on the branch.") }}</span>
79
+ {% else %}
80
+ {% set latest = releases.tags[0] %}
81
+ <span class="stat-value">{{ releases.tags|length|number }}</span>
82
+ <span class="stat-note">{% trans name=latest.name, date=latest.date|day %}Latest: {{ name }}, on <span class="nowrap">{{ date }}</span>{% endtrans %}</span>
83
+ {% endif %}
84
+ </section>
85
+ {% set tile = tiles["code_lines"] %}
86
+ <section class="stat metric" aria-labelledby="tile-code">
87
+ <span class="card-label" id="tile-code">{{ _("Code") }}</span>
88
+ <span class="stat-value">{% trans count=tile.value.numerator, lines=tile.value.numerator|number %}{{ lines }} line{% pluralize %}{{ lines }} lines{% endtrans %}</span>
89
+ <span class="stat-note">{% trans count=size.languages|length %}in {{ count }} language{% pluralize %}in {{ count }} languages{% endtrans %}</span>
90
+ <span class="stat-note">{{ change(tile) }}</span>
91
+ {{ trend(tile) }}
92
+ </section>
93
+ {% set tile = tiles["test_share"] %}
94
+ <section class="stat metric" aria-labelledby="tile-tests">
95
+ <span class="card-label" id="tile-tests">{{ _("Tests") }}</span>
96
+ <span class="stat-value">{% if tile.percent is not none %}{{ tile.percent }}%{% else %}—{% endif %}</span>
97
+ <span class="stat-note">{% trans numerator=tile.value.numerator|number, denominator=tile.value.denominator|number %}{{ numerator }} of {{ denominator }} lines{% endtrans %}</span>
98
+ {% if tile.value.denominator %}<span class="stat-note">{{ change(tile, points=true) }}</span>{% endif %}
99
+ {{ trend(tile) }}
100
+ </section>
101
+ </div>
102
+
103
+ <section id="timeline" aria-labelledby="timeline-title">
104
+ <h2 id="timeline-title">{{ _("Timeline") }}</h2>
105
+ {% if activity is none or activity.first is none %}
106
+ <p class="muted">{{ _("The history couldn't be read just now; the log says why.") }}</p>
107
+ {% else %}
108
+ <p class="muted">{{ _("The branch's whole history, by author date in UTC. Only dates and counts are read: no author or message.") }}</p>
109
+ <div class="chart-head">
110
+ <p class="chart-window" data-window-text aria-live="polite"></p>
111
+ <div class="chart-pan" data-pan hidden>
112
+ <button type="button" class="icon-button" data-pan-earlier aria-label="{{ _('Earlier') }}">‹</button>
113
+ <button type="button" class="icon-button" data-pan-later aria-label="{{ _('Later') }}">›</button>
114
+ </div>
115
+ </div>
116
+ <div class="timeline" data-chart="timeline" dir="ltr">
117
+ <p class="chart-fallback muted">{{ _("The charts need the page's script; their numbers are in the tables below.") }}</p>
118
+ </div>
119
+ <div class="release-panel" data-release-panel hidden>
120
+ <div><span class="card-label">{{ _("Release") }}</span><p class="release-name" data-release-name lang="en" dir="ltr"></p><p class="muted" data-release-meta></p></div>
121
+ <div><span class="card-label">{{ _("Notes, from the tag") }}</span><p class="release-notes" data-release-notes lang="en" dir="ltr"></p></div>
122
+ </div>
123
+ <table class="metric-table">
124
+ <tbody>
125
+ <tr><th scope="row">{{ _("First commit") }}</th><td>{{ activity.first.isoformat() }}</td></tr>
126
+ <tr><th scope="row">{{ _("Latest commit") }}</th><td>{{ activity.latest.isoformat() }} · {% trans count=[(today - activity.latest).days, 0]|max %}{{ count }} day ago{% pluralize %}{{ count }} days ago{% endtrans %}</td></tr>
127
+ <tr><th scope="row">{{ _("Weeks with a commit") }}</th><td>{% trans active=activity.active_weeks|number, weeks=activity.weeks|number %}{{ active }} of {{ weeks }}{% endtrans %}</td></tr>
128
+ <tr><th scope="row">{{ _("Longest quiet stretch") }}</th><td>{% if activity.longest_gap %}{% set start, end = activity.longest_gap %}{% trans count=(end - start).days, start=start.isoformat(), end=end.isoformat() %}{{ count }} day, from {{ start }} to {{ end }}{% pluralize %}{{ count }} days, from {{ start }} to {{ end }}{% endtrans %}{% else %}—{% endif %}</td></tr>
129
+ </tbody>
130
+ </table>
131
+ {% if activity.cut %}<p class="muted">{% trans count=history_limit|number %}The timeline shows the latest {{ count }} commits; the counts cover them all.{% endtrans %}</p>{% endif %}
132
+ <details class="more">
133
+ <summary>{{ _("Commits per month and per year, as numbers") }}</summary>
134
+ <table class="metric-table">
135
+ <thead><tr><th>{{ _("Month") }}</th><th>{{ _("Commits") }}</th></tr></thead>
136
+ <tbody>{% for month, count in all_months %}<tr><td>{{ month }}</td><td>{{ count|number }}</td></tr>{% endfor %}</tbody>
137
+ </table>
138
+ <table class="metric-table">
139
+ <thead><tr><th>{{ _("Year") }}</th><th>{{ _("Commits") }}</th></tr></thead>
140
+ <tbody>{% for year, count in activity.years %}<tr><td>{{ year }}</td><td>{{ count|number }}</td></tr>{% endfor %}</tbody>
141
+ </table>
142
+ </details>
143
+ {% endif %}
144
+ </section>
145
+
146
+ {% if activity and activity.days_start %}
147
+ <section id="calendar" aria-labelledby="calendar-title">
148
+ <h2 id="calendar-title">{{ _("Activity, day by day") }}</h2>
149
+ <p class="muted">{{ _("The last 53 weeks, a square per day, shaded by its commits.") }}</p>
150
+ <div class="calendar-layout">
151
+ <div class="calendar" data-chart="calendar" dir="ltr">
152
+ <p class="chart-fallback muted">{{ _("The charts need the page's script; their numbers are in the tables below.") }}</p>
153
+ </div>
154
+ <dl class="chart-facts">
155
+ <dt>{{ _("Weeks with a commit") }}</dt><dd>{% trans active=activity.active_weeks|number, weeks=activity.weeks|number %}{{ active }} of {{ weeks }}{% endtrans %}</dd>
156
+ <dt>{{ _("Longest quiet stretch") }}</dt><dd>{% if activity.longest_gap %}{% set start, end = activity.longest_gap %}{% trans count=(end - start).days %}{{ count }} day{% pluralize %}{{ count }} days{% endtrans %}{% else %}—{% endif %}</dd>
157
+ {% if weekday_name %}<dt>{{ _("Busiest weekday") }}</dt><dd>{{ weekday_name }}</dd>{% endif %}
158
+ <dt>{{ _("Latest commit") }}</dt><dd>{{ activity.latest.isoformat() }}</dd>
159
+ </dl>
160
+ </div>
161
+ <div class="ramp-legend" aria-hidden="true"><span>{{ _("Fewer") }}</span>{% for step in range(5) %}<span class="ramp-swatch heat-{{ step }}"></span>{% endfor %}<span>{{ _("More") }}</span></div>
162
+ <p class="chart-status muted" data-calendar-status aria-live="polite"></p>
163
+ </section>
164
+ {% endif %}
165
+
166
+ <section id="types" aria-labelledby="types-title">
167
+ <h2 id="types-title">{{ _("Commit types") }}</h2>
168
+ {% set types = report.types %}
169
+ {% if types is none %}
170
+ <p class="muted">{{ _("The commit messages couldn't be read just now; the log says why.") }}</p>
171
+ {% else %}
172
+ <p class="muted">{% trans matched=types.matched, measured=types.measured %}{{ matched }} of the latest {{ measured }} commit subjects match the target's commit_type_pattern, such as feat(web): add a page.{% endtrans %}</p>
173
+ {% if not types.used %}
174
+ <p>{{ _("The repository doesn't appear to use typed commit subjects.") }}</p>
175
+ {% else %}
176
+ {% set widest = types.types[0][1] if types.types else 0 %}
177
+ <ul class="hbars" lang="en" dir="ltr">{% for name, count in types.types %}<li><span class="hbar-label">{{ name }}</span><svg class="hbar-track" viewBox="0 0 100 12" preserveAspectRatio="none" aria-hidden="true"><rect class="series-fill-1" x="0" y="0" height="12" width="{{ (count / widest * 100)|round(1) }}" rx="2"/></svg><span class="hbar-value">{{ count|number }}</span></li>{% endfor %}</ul>
178
+ {% if types.scopes %}
179
+ <h3>{{ _("Scopes") }}</h3>
180
+ <ul class="pills" lang="en" dir="ltr">{% for name, count in types.scopes[:listed] %}<li class="pill">{{ name }} · {{ count }}</li>{% endfor %}</ul>
181
+ {% endif %}
182
+ {% endif %}
183
+ {% endif %}
184
+ </section>
185
+
186
+ <section id="releases" aria-labelledby="releases-title">
187
+ <h2 id="releases-title">{{ _("Releases") }}</h2>
188
+ {% if releases is none %}
189
+ <p class="muted">{{ _("The releases couldn't be read just now; the log says why.") }}</p>
190
+ {% elif not releases.tags %}
191
+ <p class="muted">{{ _("No releases: the branch has no tags.") }}</p>
192
+ {% else %}
193
+ <p class="muted">{{ _("The tags that point into the branch's history. A tag gitleaks flags is withheld.") }}</p>
194
+ <ul class="pills">
195
+ <li class="pill">{% trans count=releases.tags|length %}{{ count }} release{% pluralize %}{{ count }} releases{% endtrans %}</li>
196
+ <li class="pill">{% trans count=releases.since_latest %}{{ count }} commit since the latest{% pluralize %}{{ count }} commits since the latest{% endtrans %}</li>
197
+ {% if releases.median_days is not none %}<li class="pill">{% trans count=releases.median_days %}One every {{ count }} day at the median{% pluralize %}One every {{ count }} days at the median{% endtrans %}</li>{% endif %}
198
+ </ul>
199
+ {% call(tag) capped(releases.tags) %}<code>{{ tag.commit[:7] }}</code><span class="grow">{{ tag.name }}{% if tag.message %}<details class="more"><summary>{{ tag.message.split("\n", 1)[0] }}</summary><pre class="release-notes">{{ tag.message }}</pre></details>{% endif %}</span><span class="faint">{{ tag.date|day }}</span>{% endcall %}
200
+ {% endif %}
201
+ </section>
202
+
203
+ <section id="change" aria-labelledby="change-title">
204
+ <h2 id="change-title">{{ _("Where change happens") }}</h2>
205
+ {% set churn = report.churn %}
206
+ {% if churn is none %}
207
+ <p class="muted">{{ _("The history couldn't be read just now; the log says why.") }}</p>
208
+ {% else %}
209
+ <p class="muted">{% trans count=churn.commits %}Over the latest {{ count }} commit, merges left out. Only files Codetrail may read are named.{% pluralize %}Over the latest {{ count }} commits, merges left out. Only files Codetrail may read are named.{% endtrans %}</p>
210
+ <div class="chart-head">
211
+ <p class="muted">{{ _("Each tile's size is its lines of code; its shade, the commits that changed it.") }}</p>
212
+ <div class="segmented" role="group" aria-label="{{ _('Group by') }}" data-level-picker hidden>
213
+ <button type="button" data-level="areas" aria-pressed="false">{{ _("Areas") }}</button>
214
+ <button type="button" data-level="folders" aria-pressed="false">{{ _("Folders") }}</button>
215
+ <button type="button" data-level="files" aria-pressed="false">{{ _("Files") }}</button>
216
+ </div>
217
+ </div>
218
+ <div class="heatmap-layout">
219
+ <div class="heatmap-main">
220
+ <div class="heatmap" data-chart="heatmap" dir="ltr">
221
+ <p class="chart-fallback muted">{{ _("The charts need the page's script; their numbers are in the tables below.") }}</p>
222
+ </div>
223
+ {% for level, count in churn.left_out.items() %}{% if count %}<p class="muted" data-left-out="{{ level }}" hidden>{% trans count=count %}{{ count }} smaller one isn't drawn.{% pluralize %}{{ count }} smaller ones aren't drawn.{% endtrans %}</p>{% endif %}{% endfor %}
224
+ <div class="ramp-legend"><span>{{ _("Fewer commits") }}</span>{% for step in range(5) %}<span class="ramp-swatch heat-{{ step }}" aria-hidden="true"></span>{% endfor %}<span>{{ _("more") }}</span><span class="inferred-mark" aria-hidden="true"></span><span>{{ _("Mostly inferred rationale: where an ADR or a README would help most") }}</span></div>
225
+ </div>
226
+ <aside class="heat-panel" data-heat-panel aria-live="polite" hidden>
227
+ <span class="card-label" data-panel-kind></span>
228
+ <p class="heat-panel-name" data-panel-name lang="en" dir="ltr"></p>
229
+ <p class="heat-panel-path" data-panel-path lang="en" dir="ltr"></p>
230
+ <div class="heat-panel-numbers">
231
+ <div><span class="stat-value" data-panel-commits></span><span class="stat-note">{{ _("commits") }}</span></div>
232
+ <div><span class="stat-value" data-panel-lines></span><span class="stat-note">{{ _("lines of code") }}</span></div>
233
+ </div>
234
+ <div data-panel-rationale hidden>
235
+ <span class="stat-note">{{ _("Rationale in its guide page") }}</span>
236
+ <div class="split-bar" aria-hidden="true" data-panel-split><span class="split-documented" data-panel-documented-bar></span><span class="split-inferred" data-panel-inferred-bar></span></div>
237
+ <p class="split-legend"><span class="documented-text"><span data-panel-documented></span> {{ _("documented") }}</span><span class="inferred-text"><span data-panel-inferred></span> {{ _("inferred") }}</span></p>
238
+ </div>
239
+ <span class="stat-note">{{ _("Most changed files in it") }}</span>
240
+ <ul class="list" data-panel-files lang="en" dir="ltr"></ul>
241
+ <a data-panel-link hidden></a>
242
+ </aside>
243
+ </div>
244
+ {% if churn.area_months %}
245
+ <h3>{{ _("Change over time, by area") }}</h3>
246
+ <p class="muted">{{ _("Commits per month in each guide area over the last year.") }}</p>
247
+ <div class="grid-scroll">
248
+ <table class="heat-grid" data-chart="grid">
249
+ <thead><tr><th scope="col"><span class="visually-hidden">{{ _("Area") }}</span></th>{% for month in churn.months %}<th scope="col">{{ month_names[month[5:]|int - 1] }}{% if loop.first or month[5:] == "01" %} {{ month[:4] }}{% endif %}</th>{% endfor %}</tr></thead>
250
+ <tbody>
251
+ {% for page_id, cells in churn.area_months %}
252
+ <tr><th scope="row" lang="en" dir="ltr">{{ area_titles.get(page_id, page_id) }}</th>{% for count in cells %}{% if count is none %}<td class="heat-unread" data-cell="{{ area_titles.get(page_id, page_id) }} · {{ churn.months[loop.index0] }}: {{ _('not read') }}">–</td>{% else %}<td class="heat-{{ ramp_step(count, grid_highest) }}" data-cell="{{ area_titles.get(page_id, page_id) }} · {{ churn.months[loop.index0] }}: {{ count }}">{{ count if count else "" }}</td>{% endif %}{% endfor %}</tr>
253
+ {% endfor %}
254
+ </tbody>
255
+ </table>
256
+ </div>
257
+ <p class="chart-status muted" data-grid-status aria-live="polite"></p>
258
+ {% endif %}
259
+ <details class="more">
260
+ <summary>{{ _("Where change happens, as tables") }}</summary>
261
+ <h3>{{ _("Guide areas") }}</h3>
262
+ {% if churn.areas %}
263
+ <p class="muted">{{ _("An area with much change and mostly inferred rationale is where an ADR or a README would help most.") }}</p>
264
+ <table class="metric-table">
265
+ <thead><tr><th>{{ _("Area") }}</th><th>{{ _("Commits") }}</th><th>{{ _("Documented") }}</th><th>{{ _("Inferred") }}</th></tr></thead>
266
+ <tbody>
267
+ {% for spot in churn.areas[:listed] %}
268
+ <tr><td lang="en" dir="ltr"><a href="/pages/{{ spot.page.id }}">{{ spot.page.title }}</a></td><td>{{ spot.commits|number }}</td><td>{{ spot.documented }}</td><td>{{ spot.inferred }}</td></tr>
269
+ {% endfor %}
270
+ </tbody>
271
+ </table>
272
+ {% else %}
273
+ <p class="muted">{{ _("Areas are counted once your assistant has written the guide's area pages.") }}</p>
274
+ {% endif %}
275
+ {% if churn.files %}
276
+ <h3>{{ _("Most changed files") }}</h3>
277
+ <ul class="list" lang="en" dir="ltr">{% for spot in churn.files[:largest_files] %}<li><a class="grow" href="/source/{{ spot.name|urlencode }}">{{ spot.name }}</a><span class="faint">{{ spot.count|number }}</span></li>{% endfor %}</ul>
278
+ <h3>{{ _("Most changed folders") }}</h3>
279
+ <ul class="pills" lang="en" dir="ltr">{% for spot in churn.folders[:listed] %}<li class="pill">{{ spot.name }} · {{ spot.count|number }}</li>{% endfor %}</ul>
280
+ {% endif %}
281
+ </details>
282
+ <div class="change-extras">
283
+ <div>
284
+ <h3>{{ _("Commit size") }}</h3>
285
+ {% if churn.median_files is none %}
286
+ <p class="muted">{{ _("No commits to measure yet.") }}</p>
287
+ {% else %}
288
+ <p>{% trans median="%g"|format(churn.median_files), p90="%g"|format(churn.p90_files) %}Files a commit changes: {{ median }} at the median, {{ p90 }} at the 90th percentile.{% endtrans %}</p>
289
+ <figure class="histogram" dir="ltr">
290
+ {% for height in bar_heights(churn.size_histogram) %}<div class="histogram-column"><span class="histogram-count">{{ churn.size_histogram[loop.index0]|number }}</span><svg class="histogram-bar" viewBox="0 0 10 100" preserveAspectRatio="none" aria-hidden="true"><rect class="series-fill-1" x="0" y="{{ 100 - height }}" width="10" height="{{ height }}"/></svg><span class="histogram-label">{{ ["1", "2", "3", "4–5", "6–10", "11–20", "21+"][loop.index0] }}</span></div>{% endfor %}
291
+ <figcaption class="visually-hidden">{{ _("Commits by the files they change") }}</figcaption>
292
+ </figure>
293
+ {% endif %}
294
+ </div>
295
+ <div>
296
+ <h3>{{ _("Quiet code") }}</h3>
297
+ {% if churn.quiet %}
298
+ <p class="muted">{% trans count=churn.quiet_files, days=quiet_days %}{{ count }} file of code no commit has changed in the last {{ days }} days, by folder.{% pluralize %}{{ count }} files of code no commit has changed in the last {{ days }} days, by folder.{% endtrans %}</p>
299
+ <ul class="pills" lang="en" dir="ltr">{% for spot in churn.quiet[:listed] %}<li class="pill">{{ spot.name }} · {{ spot.count|number }}</li>{% endfor %}</ul>
300
+ {% else %}
301
+ <p class="muted">{% trans days=quiet_days %}Every file of code changed in the last {{ days }} days.{% endtrans %}</p>
302
+ {% endif %}
303
+ </div>
304
+ </div>
305
+ {% endif %}
306
+ </section>
307
+
308
+ <section id="size" aria-labelledby="size-title">
309
+ <h2 id="size-title">{{ _("Code size") }}</h2>
310
+ <p class="muted">{{ _("The files Codetrail may read, without the excluded ones. A file's language comes from its name; documents are the files matching the target's document_globs, and tests those matching its test_globs.") }}</p>
311
+ {% if size.languages %}
312
+ {% set segments = language_segments(size.languages) %}
313
+ <div class="stack-bar" role="img" aria-label="{% for name, lines, slot, percent in segments %}{% if name == "Other" %}{{ _("Other") }}{% else %}{{ name }}{% endif %} {{ percent }}%{% if not loop.last %}, {% endif %}{% endfor %}"><svg viewBox="0 0 1000 20" preserveAspectRatio="none" aria-hidden="true">{% set ns = namespace(x=0) %}{% for name, lines, slot, percent in segments %}{% set width = (lines / size.code_lines * 1000) if size.code_lines else 0 %}<rect class="series-fill-{{ slot }}" x="{{ ns.x|round(1) }}" y="0" height="20" width="{{ [width - 2, 0]|max|round(1) }}"/>{% set ns.x = ns.x + width %}{% endfor %}</svg></div>
314
+ <ul class="stack-legend" lang="en" dir="ltr">{% for name, lines, slot, percent in segments %}<li><span class="swatch series-{{ slot }}" aria-hidden="true"></span>{% if name == "Other" %}{{ _("Other") }}{% else %}{{ name }}{% endif %}<span class="faint">{{ percent }}% · {{ lines|number }}</span></li>{% endfor %}</ul>
315
+ {% if size.code_lines %}
316
+ <div class="share-bar" role="img" aria-label="{% trans percent=(size.test_lines / size.code_lines * 100)|round|int %}{{ percent }}% of the lines of code are tests{% endtrans %}"><svg viewBox="0 0 100 10" preserveAspectRatio="none" aria-hidden="true"><rect class="series-fill-1" x="0" y="0" height="10" width="{{ (size.test_lines / size.code_lines * 100)|round(1) }}"/></svg></div>
317
+ {% endif %}
318
+ <details class="more"><summary>{{ _("Languages, as a table") }}</summary>
319
+ <table class="metric-table">
320
+ <thead><tr><th>{{ _("Language") }}</th><th>{{ _("Files") }}</th><th>{{ _("Lines") }}</th></tr></thead>
321
+ <tbody>
322
+ {% for row in size.languages[:listed] %}
323
+ <tr><td lang="en" dir="ltr">{{ row.language }}</td><td>{{ row.files|number }}</td><td>{{ row.lines|number }}</td></tr>
324
+ {% endfor %}
325
+ </tbody>
326
+ </table>
327
+ </details>
328
+ {% endif %}
329
+ <ul class="pills">
330
+ <li class="pill pill-documented">{% trans count=size.documents.files, files=size.documents.files|number, lines=size.documents.lines|number %}{{ files }} document, {{ lines }} lines{% pluralize %}{{ files }} documents, {{ lines }} lines{% endtrans %}</li>
331
+ <li class="pill">{% trans count=size.test_lines, lines=size.test_lines|number %}{{ lines }} line of tests{% pluralize %}{{ lines }} lines of tests{% endtrans %}</li>
332
+ <li class="pill">{% trans count=size.other_files %}{{ count }} other file{% pluralize %}{{ count }} other files{% endtrans %}</li>
333
+ {% if size.binary_files %}<li class="pill">{% trans count=size.binary_files %}{{ count }} binary file{% pluralize %}{{ count }} binary files{% endtrans %}</li>{% endif %}
334
+ {% if size.too_large_files %}<li class="pill pill-stale">{% trans count=size.too_large_files %}{{ count }} file too large to count{% pluralize %}{{ count }} files too large to count{% endtrans %}</li>{% endif %}
335
+ </ul>
336
+ {% if size.largest %}
337
+ <h3>{{ _("Largest files") }}</h3>
338
+ <ul class="list" lang="en" dir="ltr">{% for file in size.largest[:largest_files] %}<li><a class="grow" href="/source/{{ file.path|urlencode }}">{{ file.path }}</a><span class="faint">{{ file.language }} · {{ file.lines|number }}</span></li>{% endfor %}</ul>
339
+ {% endif %}
340
+ </section>
341
+
342
+ <section id="facts" aria-labelledby="facts-title">
343
+ <h2 id="facts-title">{{ _("Facts") }}</h2>
344
+ {% set inventory = report.inventory %}
345
+ {% if inventory.since %}<p class="muted">{% trans date=inventory.since.taken_at[:10] %}What Codetrail's extractors found, by kind. Codetrail's history of this repository starts on {{ date }}: packages present then are counted as there from the start.{% endtrans %}</p>{% endif %}
346
+ {% if inventory.kinds %}
347
+ <ul class="fact-cards">
348
+ {% for row in inventory.kinds %}
349
+ <li class="fact-card">
350
+ <span class="stat-note" lang="en" dir="ltr">{{ row.kind }}</span>
351
+ <span><span class="stat-value">{{ row.count|number }}</span> <span class="stat-note">{% if row.change is none %}—{% else %}{{ "%+d"|format(row.change) }}{% endif %}</span></span>
352
+ <span class="spark" role="img" aria-label="{{ _('Over the last updates') }}: {{ row.trend|join(', ') }}"><svg viewBox="0 0 {{ row.trend|length * 10 }} 24" preserveAspectRatio="none" aria-hidden="true">{% for height in bar_heights(row.trend) %}{% set bar = [height, 8]|max * 0.24 %}<rect class="series-fill-1" x="{{ loop.index0 * 10 + 1 }}" y="{{ (24 - bar)|round(1) }}" width="8" height="{{ bar|round(1) }}" rx="1"/>{% endfor %}</svg></span>
353
+ </li>
354
+ {% endfor %}
355
+ </ul>
356
+ {% endif %}
357
+ {% if inventory.arrived %}
358
+ <h3>{{ _("Dependencies that arrived") }}</h3>
359
+ {% call(item) capped(inventory.arrived) %}<a class="grow" href="/facts/{{ item.package|urlencode|replace("/", "%2F") }}">{{ item.package }}</a><span class="faint">{{ item.snapshot.taken_at[:10] }} · {{ item.snapshot.commit[:7] }}</span>{% endcall %}
360
+ {% endif %}
361
+ {% if inventory.left %}
362
+ <h3>{{ _("Dependencies that left") }}</h3>
363
+ {% call(item) capped(inventory.left) %}<span class="grow">{{ item.package }}</span><span class="faint">{{ item.snapshot.taken_at[:10] }} · {{ item.snapshot.commit[:7] }}</span>{% endcall %}
364
+ {% endif %}
365
+ </section>
366
+ <script type="application/json" data-repository-data>{{ chart_json }}</script>
367
+ {% endif %}
368
+ </div>
369
+ {% endblock %}
@@ -0,0 +1,31 @@
1
+ {% extends "base.html" %}
2
+ {% from "layout/icons.html" import icon %}
3
+ {% from "macros.html" import kind_name %}
4
+ {% block title %}{{ _("Search") }}{% endblock %}
5
+ {% block content %}
6
+ <div class="wide">
7
+ <header class="page-head">
8
+ <h1>{{ _("Search") }}</h1>
9
+ <form class="search-box full" action="/search" method="get" role="search">
10
+ {{ icon("search") }}
11
+ <input type="search" name="q" value="{{ query }}" maxlength="1000" aria-label="{{ _('Search the guide') }}">
12
+ <button type="submit" class="button small">{{ _("Search") }}</button>
13
+ </form>
14
+ </header>
15
+ {% if not available %}
16
+ <p class="notice">{{ _("Search isn't available right now. The rest of the guide still works.") }}</p>
17
+ {% elif query and not results %}
18
+ <p class="muted">{% trans query=query %}Nothing in the guide matches “{{ query }}”.{% endtrans %}</p>
19
+ {% endif %}
20
+ {% if results %}
21
+ <ol class="results" lang="en" dir="ltr">
22
+ {% for result in results %}
23
+ <li>
24
+ <a href="{{ result.document.link }}">{{ result.document.title }}</a> <span class="kind" lang="{{ language.code }}" dir="{{ language.direction }}">{{ kind_name(result.document.kind) }}</span>
25
+ {% if result.snippet %}<p class="snippet">{% for text, matched in result.snippet %}{% if matched %}<mark>{{ text }}</mark>{% else %}{{ text }}{% endif %}{% endfor %}</p>{% endif %}
26
+ </li>
27
+ {% endfor %}
28
+ </ol>
29
+ {% endif %}
30
+ </div>
31
+ {% endblock %}
@@ -0,0 +1,15 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}{{ path }}{% endblock %}
3
+ {% block content %}
4
+ <header class="page-head">
5
+ <p class="eyebrow">{{ _("Source") }}</p>
6
+ <h1 lang="en" dir="ltr">{{ path }}</h1>
7
+ <p class="meta">{% trans commit=commit[:12] %}At commit {{ commit }}.{% endtrans %}</p>
8
+ </header>
9
+ {% if lines is none %}
10
+ <p class="notice">{{ _("This file isn't text, or is too large to show.") }}</p>
11
+ {% else %}
12
+ <pre class="source" lang="en" dir="ltr">{% for line in lines %}<span id="L{{ loop.index }}"><a class="line" href="#L{{ loop.index }}">{{ loop.index }}</a>{{ line }}
13
+ </span>{% endfor %}</pre>
14
+ {% endif %}
15
+ {% endblock %}
@@ -0,0 +1,43 @@
1
+ {% extends "base.html" %}
2
+ {% block title %}{{ _("Your system") }}{% endblock %}
3
+ {% block content %}
4
+ <div class="wide">
5
+ <header class="page-head">
6
+ <h1>{{ _("Your system") }}</h1>
7
+ <p class="muted">{{ _("The repository's parts and how they connect, drawn from facts: nothing here is the assistant's guess.") }}</p>
8
+ </header>
9
+
10
+ <section>
11
+ {% if diagram.rolled_up %}<p class="muted">{{ _("Too many parts to show one by one: libraries, then folders, are grouped, with the number of connections on each arrow. Every part is listed below.") }}</p>{% endif %}
12
+ {% include "diagram.html" %}
13
+ {% include "system_legend.html" %}
14
+ </section>
15
+
16
+ <section>
17
+ <h2>{{ _("Parts") }}</h2>
18
+ {% set headings = {"service": _("Services"), "app": _("Apps"), "library": _("Libraries"), "contract": _("Contracts"), "infrastructure": _("Infrastructure"), "platform": _("Platforms")} %}
19
+ {% for kind, rows in parts %}
20
+ <h3>{{ headings.get(kind, kind) }}</h3>
21
+ <table class="system-parts">
22
+ <thead><tr><th>{{ _("Part") }}</th><th>{{ _("Folder") }}</th><th>{{ _("Depends on") }}</th><th>{{ _("Called by") }}</th><th>{{ _("Runs on") }}</th></tr></thead>
23
+ <tbody>
24
+ {% for row in rows %}
25
+ <tr>
26
+ <td lang="en" dir="ltr"><a href="/facts/{{ row.id|urlencode|replace("/", "%2F") }}">{{ row.name }}</a></td>
27
+ <td lang="en" dir="ltr">{% if row.folder %}<a href="/areas/{{ row.folder|urlencode }}">{{ row.folder }}/</a>{% else %}—{% endif %}</td>
28
+ <td lang="en" dir="ltr">{{ row.depends_on | join(", ") or "—" }}</td>
29
+ <td lang="en" dir="ltr">{{ row.called_by | join(", ") or "—" }}</td>
30
+ <td lang="en" dir="ltr">{{ row.runs_on | join(", ") or "—" }}</td>
31
+ </tr>
32
+ {% endfor %}
33
+ </tbody>
34
+ </table>
35
+ {% endfor %}
36
+ </section>
37
+
38
+ <section>
39
+ <h2>{{ _("Why each arrow is there") }}</h2>
40
+ {% include "system_arrows.html" %}
41
+ </section>
42
+ </div>
43
+ {% endblock %}
@@ -0,0 +1,17 @@
1
+ {% set connections = {"depends_on": _("depends on"), "implements": _("implements"), "calls_via": _("calls through"), "deployed_on": _("is deployed on")} %}
2
+ {% if diagram.arrows %}
3
+ <ul class="list system-arrows">
4
+ {% for arrow in diagram.arrows %}
5
+ <li>
6
+ <span class="grow"><span lang="en" dir="ltr">{{ arrow.source }}</span> {{ connections.get(arrow.kind, arrow.kind) }} <span lang="en" dir="ltr">{{ arrow.target }}</span></span>
7
+ {% if arrow.evidence == "matched" %}
8
+ <span class="faint">{{ _("matched by name:") }} <span lang="en" dir="ltr">{{ arrow.names | join(" · ") }}</span>{% if arrow.link %} (<a href="{{ arrow.link }}" lang="en" dir="ltr">{{ _("source") }}</a>){% endif %}</span>
9
+ {% elif arrow.link %}
10
+ <a class="faint" href="{{ arrow.link }}" lang="en" dir="ltr">{{ arrow.link.removeprefix("/source/") }}</a>
11
+ {% endif %}
12
+ </li>
13
+ {% endfor %}
14
+ </ul>
15
+ {% else %}
16
+ <p class="muted">{{ _("No connections between the parts were found.") }}</p>
17
+ {% endif %}
@@ -0,0 +1,9 @@
1
+ <dl class="legend" aria-label="{{ _('Legend') }}">
2
+ <div><dt><span class="legend-shape legend-rounded" aria-hidden="true"></span></dt><dd>{{ _("Service or app") }}</dd></div>
3
+ <div><dt><span class="legend-shape" aria-hidden="true"></span></dt><dd>{{ _("Library") }}</dd></div>
4
+ <div><dt><span class="legend-shape legend-document" aria-hidden="true"></span></dt><dd>{{ _("Contract") }}</dd></div>
5
+ <div><dt><span class="legend-shape legend-double" aria-hidden="true"></span></dt><dd>{{ _("Infrastructure") }}</dd></div>
6
+ <div><dt><span class="legend-shape legend-stadium" aria-hidden="true"></span></dt><dd>{{ _("Platform") }}</dd></div>
7
+ <div><dt><span class="legend-line" aria-hidden="true"></span></dt><dd>{{ _("Found in a file you can open") }}</dd></div>
8
+ <div><dt><span class="legend-line legend-dashed" aria-hidden="true"></span></dt><dd>{{ _("Matched by name: likely, not certain") }}</dd></div>
9
+ </dl>
@@ -0,0 +1,30 @@
1
+ {# The update's steps, for the update panel (design section 15.4). Titles and reasons come from the assistant and
2
+ the repository, so they are only ever autoescaped text. #}
3
+ {% macro used(step) -%}
4
+ <span class="muted">{% if step.provider %} · {{ step.provider }} · {{ step.model or _("default model") }}{% endif %}{% if step.tokens %} · {{ step.tokens|tokens }} {{ _("tokens") }}{% endif %}{% if step.cost_usd %} · {{ step.cost_usd|dollars }}{% endif %}</span>
5
+ {%- endmacro %}
6
+ {% for step in steps %}
7
+ {% if step.step == "facts" %}
8
+ <li class="update-line">{{ _("Refreshing the facts (free)…") }}</li>
9
+ {% elif step.step == "facts_recorded" %}
10
+ <li class="update-line">{% if step.changes %}{% trans count=step.changes %}The facts are refreshed: one changed.{% pluralize %}The facts are refreshed: {{ count }} changed.{% endtrans %}{% else %}{{ _("The facts are refreshed: none changed.") }}{% endif %}</li>
11
+ {% elif step.step == "plan" %}
12
+ <li class="update-line">{{ _("Planning the outline…") }}</li>
13
+ {% elif step.step == "planned" %}
14
+ <li class="update-line done">{% trans count=step.pages %}Planned one new page.{% pluralize %}Planned {{ count }} new pages.{% endtrans %}{{ used(step) }}</li>
15
+ {% elif step.step == "page" %}
16
+ <li class="update-line">{% if step.attempt != 1 and step.revise %}{% trans title=step.title %}Revising “{{ title }}” again, with its problems…{% endtrans %}{% elif step.attempt != 1 %}{% trans title=step.title %}Writing “{{ title }}” again, with its problems…{% endtrans %}{% elif step.revise and step.reason == "update" %}{% trans title=step.title, count=step.changed %}Revising “{{ title }}” (one of its facts changed in this update)…{% pluralize count %}Revising “{{ title }}” ({{ count }} of its facts changed in this update)…{% endtrans %}{% elif step.revise %}{% trans title=step.title %}Revising “{{ title }}” (catching up: its facts changed after it was written)…{% endtrans %}{% elif step.reason == "update" %}{% trans title=step.title, count=step.changed %}Writing “{{ title }}” (one of its facts changed in this update)…{% pluralize count %}Writing “{{ title }}” ({{ count }} of its facts changed in this update)…{% endtrans %}{% elif step.reason == "new" %}{% trans title=step.title %}Writing “{{ title }}” (a new page)…{% endtrans %}{% elif step.reason == "outline" %}{% trans title=step.title %}Writing “{{ title }}” (its outline entry changed)…{% endtrans %}{% elif step.reason == "catching_up" %}{% trans title=step.title %}Writing “{{ title }}” (catching up: its facts changed after it was written)…{% endtrans %}{% else %}{% trans title=step.title %}Writing “{{ title }}”…{% endtrans %}{% endif %}</li>
17
+ {% elif step.step == "page_written" %}
18
+ <li class="update-line done">{% if step.unchanged %}{% trans title=step.title %}“{{ title }}” needed no change.{% endtrans %}{% elif step.revise %}{% trans title=step.title %}Revised “{{ title }}”.{% endtrans %}{% else %}{% trans title=step.title %}Wrote “{{ title }}”.{% endtrans %}{% endif %}{{ used(step) }}</li>
19
+ {% elif step.step == "page_failed" %}
20
+ <li class="update-line failed">{% trans title=step.title, reason=step.reason %}“{{ title }}” wasn’t written: {{ reason }}{% endtrans %}</li>
21
+ {% elif step.step == "page_later" %}
22
+ <li class="update-line">{% trans title=step.title %}“{{ title }}” waits for the next update: the budget is spent.{% endtrans %}</li>
23
+ {% elif step.step == "page_skipped" %}
24
+ <li class="update-line">{% trans title=step.title %}“{{ title }}” is skipped: it failed last time, and nothing in it changed since.{% endtrans %} {% trans name=target %}To write it anyway: <code>codetrail update {{ name }} --retry-failed</code>{% endtrans %}</li>
25
+ {% elif step.step == "digest" %}
26
+ <li class="update-line">{{ _("Writing the digest…") }}</li>
27
+ {% elif step.step == "committed" %}
28
+ <li class="update-line done">{% trans count=step.pages %}Saved one page to the guide.{% pluralize %}Saved {{ count }} pages to the guide.{% endtrans %}{% if step.failed %} {% trans count=step.failed %}One page wasn’t written.{% pluralize %}{{ count }} pages weren’t written.{% endtrans %}{% endif %}{% if step.later %} {% trans count=step.later %}One waits for the next update.{% pluralize %}{{ count }} wait for the next update.{% endtrans %}{% endif %}</li>
29
+ {% endif %}
30
+ {% endfor %}
codetrail/web/trend.py ADDED
@@ -0,0 +1,22 @@
1
+ """Trend lines (design section 18.4): SVG points drawn on the server from numbers only."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+
7
+ PADDING = 2
8
+
9
+
10
+ def trend_points(values: Sequence[float], width: int = 120, height: int = 32) -> str:
11
+ """The `points` of a polyline, oldest value on the left, the lowest at the bottom; one value draws a flat line."""
12
+ if not values:
13
+ return ""
14
+ low, high = min(values), max(values)
15
+
16
+ def y(value: float) -> float:
17
+ middle = 0.5 if high == low else (high - value) / (high - low)
18
+ return PADDING + (height - 2 * PADDING) * middle
19
+
20
+ drawn = list(values) if len(values) > 1 else [values[0], values[0]]
21
+ step = (width - 2 * PADDING) / (len(drawn) - 1)
22
+ return " ".join(f"{PADDING + index * step:.1f},{y(value):.1f}" for index, value in enumerate(drawn))