chronicle-devkit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chronicle_devkit-0.1.0/PKG-INFO +233 -0
- chronicle_devkit-0.1.0/README.md +217 -0
- chronicle_devkit-0.1.0/pyproject.toml +30 -0
- chronicle_devkit-0.1.0/setup.cfg +4 -0
- chronicle_devkit-0.1.0/src/chronicle/__init__.py +1 -0
- chronicle_devkit-0.1.0/src/chronicle/ai/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/ai/base.py +7 -0
- chronicle_devkit-0.1.0/src/chronicle/ai/factory.py +23 -0
- chronicle_devkit-0.1.0/src/chronicle/ai/google_ai.py +14 -0
- chronicle_devkit-0.1.0/src/chronicle/ai/openai.py +14 -0
- chronicle_devkit-0.1.0/src/chronicle/analysis/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/analysis/analyzers/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/analysis/analyzers/base.py +15 -0
- chronicle_devkit-0.1.0/src/chronicle/analysis/analyzers/django_migrations.py +74 -0
- chronicle_devkit-0.1.0/src/chronicle/analysis/analyzers/git.py +37 -0
- chronicle_devkit-0.1.0/src/chronicle/analysis/context.py +12 -0
- chronicle_devkit-0.1.0/src/chronicle/analysis/engine.py +18 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/__init__.py +1 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/commands/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/commands/analyze.py +73 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/commands/config.py +79 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/commands/init.py +47 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/commands/interpret.py +48 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/commands/scan.py +87 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/commands/show.py +106 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/commands/status.py +26 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/commands/version.py +6 -0
- chronicle_devkit-0.1.0/src/chronicle/cli/main.py +30 -0
- chronicle_devkit-0.1.0/src/chronicle/config/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/config/credentials.py +17 -0
- chronicle_devkit-0.1.0/src/chronicle/config/loader.py +21 -0
- chronicle_devkit-0.1.0/src/chronicle/config/manager.py +52 -0
- chronicle_devkit-0.1.0/src/chronicle/integrations/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/integrations/git.py +16 -0
- chronicle_devkit-0.1.0/src/chronicle/interpretation/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/interpretation/context.py +8 -0
- chronicle_devkit-0.1.0/src/chronicle/interpretation/context_builder.py +147 -0
- chronicle_devkit-0.1.0/src/chronicle/interpretation/interpreter.py +12 -0
- chronicle_devkit-0.1.0/src/chronicle/interpretation/prompts.py +51 -0
- chronicle_devkit-0.1.0/src/chronicle/project/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/project/discovery.py +9 -0
- chronicle_devkit-0.1.0/src/chronicle/project/initializer.py +45 -0
- chronicle_devkit-0.1.0/src/chronicle/project/status.py +25 -0
- chronicle_devkit-0.1.0/src/chronicle/scanning/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/scanning/context.py +10 -0
- chronicle_devkit-0.1.0/src/chronicle/scanning/engine.py +16 -0
- chronicle_devkit-0.1.0/src/chronicle/scanning/scanners/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/scanning/scanners/base.py +17 -0
- chronicle_devkit-0.1.0/src/chronicle/scanning/scanners/django_migrations.py +156 -0
- chronicle_devkit-0.1.0/src/chronicle/scanning/scanners/django_model.py +12 -0
- chronicle_devkit-0.1.0/src/chronicle/scanning/scanners/git.py +94 -0
- chronicle_devkit-0.1.0/src/chronicle/scanning/scanners/git_models.py +6 -0
- chronicle_devkit-0.1.0/src/chronicle/storage/__init__.py +0 -0
- chronicle_devkit-0.1.0/src/chronicle/storage/analysis_state.py +44 -0
- chronicle_devkit-0.1.0/src/chronicle/storage/database.py +9 -0
- chronicle_devkit-0.1.0/src/chronicle/storage/findings.py +53 -0
- chronicle_devkit-0.1.0/src/chronicle/storage/models.py +20 -0
- chronicle_devkit-0.1.0/src/chronicle/storage/observations.py +93 -0
- chronicle_devkit-0.1.0/src/chronicle/storage/scan_state.py +44 -0
- chronicle_devkit-0.1.0/src/chronicle/storage/schema.py +47 -0
- chronicle_devkit-0.1.0/src/chronicle_devkit.egg-info/PKG-INFO +233 -0
- chronicle_devkit-0.1.0/src/chronicle_devkit.egg-info/SOURCES.txt +64 -0
- chronicle_devkit-0.1.0/src/chronicle_devkit.egg-info/dependency_links.txt +1 -0
- chronicle_devkit-0.1.0/src/chronicle_devkit.egg-info/entry_points.txt +2 -0
- chronicle_devkit-0.1.0/src/chronicle_devkit.egg-info/requires.txt +12 -0
- chronicle_devkit-0.1.0/src/chronicle_devkit.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chronicle-devkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A local first developer intelligence tool
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: typer>=0.16
|
|
8
|
+
Requires-Dist: tomli-w>=1.2.0
|
|
9
|
+
Requires-Dist: openai>=3.6.0
|
|
10
|
+
Requires-Dist: nltk>=3.10.3
|
|
11
|
+
Requires-Dist: google-genai>=2.20.0
|
|
12
|
+
Requires-Dist: keyring>=25.7.0
|
|
13
|
+
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
16
|
+
|
|
17
|
+
# Chronicle
|
|
18
|
+
|
|
19
|
+
**Local-first developer intelligence.** Chronicle records how a software project changes over time, turns that history into signals, and lets you ask questions about it — all stored and analyzed on your own machine.
|
|
20
|
+
|
|
21
|
+
Chronicle fits naturally into your existing workflow. Point it at a Git project, and it:
|
|
22
|
+
|
|
23
|
+
1. **Scans** your project's history into structured *observations* (git commits, Django migrations).
|
|
24
|
+
2. **Analyzes** those observations into actionable *findings* (new files, changed schema fields …).
|
|
25
|
+
3. **Interprets** the collected history with AI, answering questions in plain language.
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
git history ──► scan ──► observations ──► analyze ──► findings ──► interpret (AI) ──► answers
|
|
31
|
+
Django migrations ─┘ local SQLite ▲
|
|
32
|
+
keywords + related commits
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- **Local-first** — everything lives in a `.chronicle/` folder inside your project. No accounts, no cloud sync, no telemetry. Your `chronicle.db` stays yours.
|
|
36
|
+
- **Incremental** — Chronicle remembers how far it got (`scan_state` for scanners, `analysis_state` for analyzers) so repeated runs only process what's new.
|
|
37
|
+
- **Extensible** — scanners (`Scanner`) and analyzers (`BaseAnalyzer`) are pluggable; providers are pluggable too (OpenAI / Gemini).
|
|
38
|
+
|
|
39
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
- **Python 3.10+**
|
|
42
|
+
- A **Git** repository (the project root is detected by walking up to the nearest `.git` directory)
|
|
43
|
+
- `pip` via the `dev` extra for running tests
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# clone and enter the repository
|
|
49
|
+
git clone https://github.com/Sakshyam-Xtha/Chronicle.git
|
|
50
|
+
cd Chronicle
|
|
51
|
+
|
|
52
|
+
# create a virtual environment (optional but recommended)
|
|
53
|
+
python -m venv .venv
|
|
54
|
+
source .venv/bin/activate
|
|
55
|
+
|
|
56
|
+
# install in editable mode (includes the `chronicle` command)
|
|
57
|
+
pip install -e ".[dev]"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Verify the CLI is on your path:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
chronicle version # -> Chronicle 0.1.0
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quick start
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 1. Initialize Chronicle in a project (creates .chronicle/ + local DB)
|
|
70
|
+
cd /path/to/your/project
|
|
71
|
+
chronicle init
|
|
72
|
+
|
|
73
|
+
# 2. Scan the project history into observations
|
|
74
|
+
chronicle scan
|
|
75
|
+
|
|
76
|
+
# 3. Browse what was recorded
|
|
77
|
+
chronicle show
|
|
78
|
+
chronicle show --id 1
|
|
79
|
+
|
|
80
|
+
# 4. Analyze observations into findings
|
|
81
|
+
chronicle analyze
|
|
82
|
+
|
|
83
|
+
# 5. Ask questions about your history (requires an AI provider/key)
|
|
84
|
+
chronicle interpret --question "When did we add the User model?"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
> `init`, `scan`, and `show` need no configuration. Only `interpret` requires an AI provider.
|
|
88
|
+
|
|
89
|
+
## Command reference
|
|
90
|
+
|
|
91
|
+
| Command | Description |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `chronicle init` | Create the `.chronicle/` directory and initialize the local database |
|
|
94
|
+
| `chronicle scan` | Scan project history into observations (git commits, Django migrations) |
|
|
95
|
+
| `chronicle show` | List all observations; `--id <n>` shows one observation in detail |
|
|
96
|
+
| `chronicle analyze` | Turn observations into findings via the installed analyzers |
|
|
97
|
+
| `chronicle interpret --question "<text>"` | Ask an AI-backed question about the project history |
|
|
98
|
+
| `chronicle config` | View current configuration; `set provider` / `set model` to configure AI |
|
|
99
|
+
| `chronicle status` | Show whether the project is initialized and detected |
|
|
100
|
+
| `chronicle version` | Print the Chronicle version |
|
|
101
|
+
|
|
102
|
+
### `chronicle status`
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
$ chronicle status
|
|
106
|
+
Project: my-project
|
|
107
|
+
Root: /path/to/my-project
|
|
108
|
+
Git: detected
|
|
109
|
+
Chronicle: initialized
|
|
110
|
+
Configuration: found
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `chronicle show`
|
|
114
|
+
|
|
115
|
+
Without arguments it prints a compact table of all observations. With `--id <n>` it renders a detailed view:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
Observation #1
|
|
119
|
+
────────────────────────────────────────────
|
|
120
|
+
Source: git
|
|
121
|
+
Type: commit
|
|
122
|
+
External ID: 9f8d3a1
|
|
123
|
+
Timestamp: 2026-08-21 12:00:00 UTC
|
|
124
|
+
|
|
125
|
+
Data
|
|
126
|
+
────────────────────────────────────────────
|
|
127
|
+
```
|
|
128
|
+
Migration observations additionally show **App**, **Migration**, **Dependencies**, and **Operations** (model / field per operation).
|
|
129
|
+
|
|
130
|
+
## Configuring AI (for `interpret`)
|
|
131
|
+
|
|
132
|
+
Chronicle reads its configuration from `.chronicle/config.toml`:
|
|
133
|
+
|
|
134
|
+
```toml
|
|
135
|
+
[chronicle]
|
|
136
|
+
version = 1
|
|
137
|
+
|
|
138
|
+
[ai]
|
|
139
|
+
provider = ""
|
|
140
|
+
model = ""
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Set the provider and model:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
chronicle config set provider openai
|
|
147
|
+
chronicle config set model gpt-4o
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Then export your API key as an environment variable:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
export OPENAI_API_KEY="sk-..." # provider: openai
|
|
154
|
+
export GEMINI_API_KEY="..." # provider: gemini
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Check everything is wired up:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
chronicle config
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## What gets recorded
|
|
164
|
+
|
|
165
|
+
### Git commits (`scan`)
|
|
166
|
+
|
|
167
|
+
Each commit becomes an observation with:
|
|
168
|
+
|
|
169
|
+
- `hash`, `message`, `author`
|
|
170
|
+
- `parents` (empty for the root commit, one or more for merges)
|
|
171
|
+
- `changes` — per-file **status** (`A` added, `M` modified, `D` deleted) and **path**
|
|
172
|
+
|
|
173
|
+
### Django migrations (`scan`)
|
|
174
|
+
|
|
175
|
+
Every `migrations/*.py` file (ignoring `.git`, `.venv`, `venv`, `env`, `node_modules`, `__pycache__`) is parsed with `ast` into an observation with:
|
|
176
|
+
|
|
177
|
+
- `app` (application label), `name` (migration name)
|
|
178
|
+
- `dependencies`
|
|
179
|
+
- `operations` — e.g. `AddField`, `RemoveField` with model / field details
|
|
180
|
+
|
|
181
|
+
### Findings (`analyze`)
|
|
182
|
+
|
|
183
|
+
Analyzers turn observations into findings:
|
|
184
|
+
|
|
185
|
+
- **GitAnalyzer** — flags newly created files (`A`) with `severity: info`
|
|
186
|
+
- **DjangoMigrationAnalyzer** — flags `RemoveField` (`warning`) and `AddField` (`info`) operations
|
|
187
|
+
|
|
188
|
+
## How interpretation works
|
|
189
|
+
|
|
190
|
+
`chronicle interpret` does **not** dump everything at the model. It:
|
|
191
|
+
|
|
192
|
+
1. Tokenizes your question and drops stop words to extract keywords.
|
|
193
|
+
2. Scores every finding by how well it matches those keywords (title, message, data).
|
|
194
|
+
3. Picks the top 10 findings and their related git commits (e.g. the commit that introduced a migration).
|
|
195
|
+
4. Builds a prompt with only that focused context and asks the configured provider.
|
|
196
|
+
|
|
197
|
+
## Project layout
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
src/chronicle/
|
|
201
|
+
├── ai/ # AI provider abstraction (OpenAI, Gemini, factory)
|
|
202
|
+
├── analysis/ # analyzers + engine that turn observations into findings
|
|
203
|
+
├── cli/ # Typer CLI commands (`scan`, `show`, `analyze`, …)
|
|
204
|
+
├── config/ # config.toml management + API key resolution
|
|
205
|
+
├── integrations/ # thin wrappers (e.g. git)
|
|
206
|
+
├── interpretation/ # question -> context -> prompt -> AI response
|
|
207
|
+
├── project/ # discovery, initialization, status
|
|
208
|
+
├── scanning/ # scanners + engine that collect observations
|
|
209
|
+
│ └── scanners/ # git, django_migrations and their models
|
|
210
|
+
└── storage/ # SQLite repositories + schema
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
Run the test suite:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
pip install -e ".[dev]"
|
|
219
|
+
pytest test/
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The suite is split into:
|
|
223
|
+
|
|
224
|
+
- `test/unit/` — fast, mocked tests (parsing, storage, contexts, models)
|
|
225
|
+
- `test/integration/` — real-git and end-to-end CLI tests (incremental scan checkpoints, migration parsing, merge parents)
|
|
226
|
+
|
|
227
|
+
## Roadmap / status
|
|
228
|
+
|
|
229
|
+
Chronicle is an early-stage local-first tool (`0.1.0`). Current scanners cover **git** and **Django migrations**; analyzers cover **git file additions** and **Django schema changes**; AI interpretation supports **OpenAI** and **Gemini**.
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
Not yet specified.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Chronicle
|
|
2
|
+
|
|
3
|
+
**Local-first developer intelligence.** Chronicle records how a software project changes over time, turns that history into signals, and lets you ask questions about it — all stored and analyzed on your own machine.
|
|
4
|
+
|
|
5
|
+
Chronicle fits naturally into your existing workflow. Point it at a Git project, and it:
|
|
6
|
+
|
|
7
|
+
1. **Scans** your project's history into structured *observations* (git commits, Django migrations).
|
|
8
|
+
2. **Analyzes** those observations into actionable *findings* (new files, changed schema fields …).
|
|
9
|
+
3. **Interprets** the collected history with AI, answering questions in plain language.
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
git history ──► scan ──► observations ──► analyze ──► findings ──► interpret (AI) ──► answers
|
|
15
|
+
Django migrations ─┘ local SQLite ▲
|
|
16
|
+
keywords + related commits
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- **Local-first** — everything lives in a `.chronicle/` folder inside your project. No accounts, no cloud sync, no telemetry. Your `chronicle.db` stays yours.
|
|
20
|
+
- **Incremental** — Chronicle remembers how far it got (`scan_state` for scanners, `analysis_state` for analyzers) so repeated runs only process what's new.
|
|
21
|
+
- **Extensible** — scanners (`Scanner`) and analyzers (`BaseAnalyzer`) are pluggable; providers are pluggable too (OpenAI / Gemini).
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- **Python 3.10+**
|
|
26
|
+
- A **Git** repository (the project root is detected by walking up to the nearest `.git` directory)
|
|
27
|
+
- `pip` via the `dev` extra for running tests
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# clone and enter the repository
|
|
33
|
+
git clone https://github.com/Sakshyam-Xtha/Chronicle.git
|
|
34
|
+
cd Chronicle
|
|
35
|
+
|
|
36
|
+
# create a virtual environment (optional but recommended)
|
|
37
|
+
python -m venv .venv
|
|
38
|
+
source .venv/bin/activate
|
|
39
|
+
|
|
40
|
+
# install in editable mode (includes the `chronicle` command)
|
|
41
|
+
pip install -e ".[dev]"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Verify the CLI is on your path:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
chronicle version # -> Chronicle 0.1.0
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 1. Initialize Chronicle in a project (creates .chronicle/ + local DB)
|
|
54
|
+
cd /path/to/your/project
|
|
55
|
+
chronicle init
|
|
56
|
+
|
|
57
|
+
# 2. Scan the project history into observations
|
|
58
|
+
chronicle scan
|
|
59
|
+
|
|
60
|
+
# 3. Browse what was recorded
|
|
61
|
+
chronicle show
|
|
62
|
+
chronicle show --id 1
|
|
63
|
+
|
|
64
|
+
# 4. Analyze observations into findings
|
|
65
|
+
chronicle analyze
|
|
66
|
+
|
|
67
|
+
# 5. Ask questions about your history (requires an AI provider/key)
|
|
68
|
+
chronicle interpret --question "When did we add the User model?"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> `init`, `scan`, and `show` need no configuration. Only `interpret` requires an AI provider.
|
|
72
|
+
|
|
73
|
+
## Command reference
|
|
74
|
+
|
|
75
|
+
| Command | Description |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| `chronicle init` | Create the `.chronicle/` directory and initialize the local database |
|
|
78
|
+
| `chronicle scan` | Scan project history into observations (git commits, Django migrations) |
|
|
79
|
+
| `chronicle show` | List all observations; `--id <n>` shows one observation in detail |
|
|
80
|
+
| `chronicle analyze` | Turn observations into findings via the installed analyzers |
|
|
81
|
+
| `chronicle interpret --question "<text>"` | Ask an AI-backed question about the project history |
|
|
82
|
+
| `chronicle config` | View current configuration; `set provider` / `set model` to configure AI |
|
|
83
|
+
| `chronicle status` | Show whether the project is initialized and detected |
|
|
84
|
+
| `chronicle version` | Print the Chronicle version |
|
|
85
|
+
|
|
86
|
+
### `chronicle status`
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
$ chronicle status
|
|
90
|
+
Project: my-project
|
|
91
|
+
Root: /path/to/my-project
|
|
92
|
+
Git: detected
|
|
93
|
+
Chronicle: initialized
|
|
94
|
+
Configuration: found
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `chronicle show`
|
|
98
|
+
|
|
99
|
+
Without arguments it prints a compact table of all observations. With `--id <n>` it renders a detailed view:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
Observation #1
|
|
103
|
+
────────────────────────────────────────────
|
|
104
|
+
Source: git
|
|
105
|
+
Type: commit
|
|
106
|
+
External ID: 9f8d3a1
|
|
107
|
+
Timestamp: 2026-08-21 12:00:00 UTC
|
|
108
|
+
|
|
109
|
+
Data
|
|
110
|
+
────────────────────────────────────────────
|
|
111
|
+
```
|
|
112
|
+
Migration observations additionally show **App**, **Migration**, **Dependencies**, and **Operations** (model / field per operation).
|
|
113
|
+
|
|
114
|
+
## Configuring AI (for `interpret`)
|
|
115
|
+
|
|
116
|
+
Chronicle reads its configuration from `.chronicle/config.toml`:
|
|
117
|
+
|
|
118
|
+
```toml
|
|
119
|
+
[chronicle]
|
|
120
|
+
version = 1
|
|
121
|
+
|
|
122
|
+
[ai]
|
|
123
|
+
provider = ""
|
|
124
|
+
model = ""
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Set the provider and model:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
chronicle config set provider openai
|
|
131
|
+
chronicle config set model gpt-4o
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Then export your API key as an environment variable:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
export OPENAI_API_KEY="sk-..." # provider: openai
|
|
138
|
+
export GEMINI_API_KEY="..." # provider: gemini
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Check everything is wired up:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
chronicle config
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## What gets recorded
|
|
148
|
+
|
|
149
|
+
### Git commits (`scan`)
|
|
150
|
+
|
|
151
|
+
Each commit becomes an observation with:
|
|
152
|
+
|
|
153
|
+
- `hash`, `message`, `author`
|
|
154
|
+
- `parents` (empty for the root commit, one or more for merges)
|
|
155
|
+
- `changes` — per-file **status** (`A` added, `M` modified, `D` deleted) and **path**
|
|
156
|
+
|
|
157
|
+
### Django migrations (`scan`)
|
|
158
|
+
|
|
159
|
+
Every `migrations/*.py` file (ignoring `.git`, `.venv`, `venv`, `env`, `node_modules`, `__pycache__`) is parsed with `ast` into an observation with:
|
|
160
|
+
|
|
161
|
+
- `app` (application label), `name` (migration name)
|
|
162
|
+
- `dependencies`
|
|
163
|
+
- `operations` — e.g. `AddField`, `RemoveField` with model / field details
|
|
164
|
+
|
|
165
|
+
### Findings (`analyze`)
|
|
166
|
+
|
|
167
|
+
Analyzers turn observations into findings:
|
|
168
|
+
|
|
169
|
+
- **GitAnalyzer** — flags newly created files (`A`) with `severity: info`
|
|
170
|
+
- **DjangoMigrationAnalyzer** — flags `RemoveField` (`warning`) and `AddField` (`info`) operations
|
|
171
|
+
|
|
172
|
+
## How interpretation works
|
|
173
|
+
|
|
174
|
+
`chronicle interpret` does **not** dump everything at the model. It:
|
|
175
|
+
|
|
176
|
+
1. Tokenizes your question and drops stop words to extract keywords.
|
|
177
|
+
2. Scores every finding by how well it matches those keywords (title, message, data).
|
|
178
|
+
3. Picks the top 10 findings and their related git commits (e.g. the commit that introduced a migration).
|
|
179
|
+
4. Builds a prompt with only that focused context and asks the configured provider.
|
|
180
|
+
|
|
181
|
+
## Project layout
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
src/chronicle/
|
|
185
|
+
├── ai/ # AI provider abstraction (OpenAI, Gemini, factory)
|
|
186
|
+
├── analysis/ # analyzers + engine that turn observations into findings
|
|
187
|
+
├── cli/ # Typer CLI commands (`scan`, `show`, `analyze`, …)
|
|
188
|
+
├── config/ # config.toml management + API key resolution
|
|
189
|
+
├── integrations/ # thin wrappers (e.g. git)
|
|
190
|
+
├── interpretation/ # question -> context -> prompt -> AI response
|
|
191
|
+
├── project/ # discovery, initialization, status
|
|
192
|
+
├── scanning/ # scanners + engine that collect observations
|
|
193
|
+
│ └── scanners/ # git, django_migrations and their models
|
|
194
|
+
└── storage/ # SQLite repositories + schema
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
Run the test suite:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pip install -e ".[dev]"
|
|
203
|
+
pytest test/
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The suite is split into:
|
|
207
|
+
|
|
208
|
+
- `test/unit/` — fast, mocked tests (parsing, storage, contexts, models)
|
|
209
|
+
- `test/integration/` — real-git and end-to-end CLI tests (incremental scan checkpoints, migration parsing, merge parents)
|
|
210
|
+
|
|
211
|
+
## Roadmap / status
|
|
212
|
+
|
|
213
|
+
Chronicle is an early-stage local-first tool (`0.1.0`). Current scanners cover **git** and **Django migrations**; analyzers cover **git file additions** and **Django schema changes**; AI interpretation supports **OpenAI** and **Gemini**.
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
Not yet specified.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "chronicle-devkit"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.10"
|
|
5
|
+
description = "A local first developer intelligence tool"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"typer>=0.16",
|
|
8
|
+
"tomli-w>=1.2.0",
|
|
9
|
+
"openai>=3.6.0",
|
|
10
|
+
"nltk>=3.10.3",
|
|
11
|
+
"google-genai>=2.20.0",
|
|
12
|
+
"keyring>=25.7.0",
|
|
13
|
+
"tomli>=2.0.0; python_version < '3.11'",
|
|
14
|
+
]
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["setuptools>=68"]
|
|
24
|
+
build-backend = "setuptools.build_meta"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
chronicle = "chronicle.cli.main:main"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from chronicle.ai.base import AIProvider
|
|
2
|
+
from chronicle.ai.openai import OpenAIProvider
|
|
3
|
+
from chronicle.ai.google_ai import GenAIProvider
|
|
4
|
+
|
|
5
|
+
def create_provider(
|
|
6
|
+
provider: str,
|
|
7
|
+
model:str,
|
|
8
|
+
api_key:str
|
|
9
|
+
) -> AIProvider:
|
|
10
|
+
if provider == "openai":
|
|
11
|
+
return OpenAIProvider(
|
|
12
|
+
api_key=api_key,
|
|
13
|
+
model=model,
|
|
14
|
+
)
|
|
15
|
+
elif provider == "gemini":
|
|
16
|
+
return GenAIProvider(
|
|
17
|
+
api_key=api_key,
|
|
18
|
+
model=model,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
raise ValueError(
|
|
22
|
+
f"Unsupported AI provider: {provider}"
|
|
23
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from google import genai
|
|
2
|
+
from chronicle.ai.base import AIProvider
|
|
3
|
+
|
|
4
|
+
class GenAIProvider(AIProvider):
|
|
5
|
+
def __init__(self,model:str,api_key:str) -> None:
|
|
6
|
+
self.client = genai.Client(api_key=api_key)
|
|
7
|
+
self.model = model
|
|
8
|
+
|
|
9
|
+
def generate(self, prompt: str) -> str:
|
|
10
|
+
response = self.client.models.generate_content(
|
|
11
|
+
model=self.model,
|
|
12
|
+
contents = prompt,
|
|
13
|
+
)
|
|
14
|
+
return response.text if response.text else "Error: Model did not respond"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from openai import OpenAI
|
|
2
|
+
from chronicle.ai.base import AIProvider
|
|
3
|
+
|
|
4
|
+
class OpenAIProvider(AIProvider):
|
|
5
|
+
def __init__(self,api_key:str,model:str) -> None:
|
|
6
|
+
self.client = OpenAI(api_key=api_key)
|
|
7
|
+
self.model = model
|
|
8
|
+
|
|
9
|
+
def generate(self, prompt: str) -> str:
|
|
10
|
+
response = self.client.responses.create(
|
|
11
|
+
model=self.model,
|
|
12
|
+
input=prompt,
|
|
13
|
+
)
|
|
14
|
+
return response.output_text
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from abc import ABC,abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from chronicle.storage.models import Observation,Findings
|
|
4
|
+
from chronicle.analysis.context import AnalysisContext
|
|
5
|
+
|
|
6
|
+
class BaseAnalyzer(ABC):
|
|
7
|
+
"""Base class for all chronicle analyzers."""
|
|
8
|
+
|
|
9
|
+
def __init__(self,project_root:Path) -> None:
|
|
10
|
+
self.project_root = project_root
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def analyze(self, context:AnalysisContext) -> list[Findings]:
|
|
14
|
+
"""Analyze the project and return findings."""
|
|
15
|
+
pass
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from chronicle.analysis.analyzers.base import BaseAnalyzer
|
|
2
|
+
from chronicle.storage.models import Findings
|
|
3
|
+
import typer
|
|
4
|
+
from chronicle.storage.observations import Observation
|
|
5
|
+
from chronicle.analysis.context import AnalysisContext
|
|
6
|
+
|
|
7
|
+
class DjangoMigrationAnalyzer(BaseAnalyzer):
|
|
8
|
+
|
|
9
|
+
def analyze(
|
|
10
|
+
self,
|
|
11
|
+
context: AnalysisContext
|
|
12
|
+
) -> list[Findings]:
|
|
13
|
+
|
|
14
|
+
findings = []
|
|
15
|
+
|
|
16
|
+
for observation in context.observations:
|
|
17
|
+
|
|
18
|
+
if observation.source != "django":
|
|
19
|
+
continue
|
|
20
|
+
|
|
21
|
+
if observation.type != "migration":
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
operations = observation.data.get(
|
|
25
|
+
"operations",
|
|
26
|
+
[]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
for operation in operations:
|
|
30
|
+
|
|
31
|
+
operation_name = operation.get(
|
|
32
|
+
"operation"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if operation_name == "RemoveField":
|
|
36
|
+
typer.echo(
|
|
37
|
+
f"Generating finding: "
|
|
38
|
+
f"observation={observation.id}, "
|
|
39
|
+
f"data={operation}"
|
|
40
|
+
)
|
|
41
|
+
findings.append(
|
|
42
|
+
Findings(
|
|
43
|
+
analyzer="django-migrations",
|
|
44
|
+
severity="warning",
|
|
45
|
+
title="Field removed",
|
|
46
|
+
message=(
|
|
47
|
+
"A database field is being "
|
|
48
|
+
"removed by this migration."
|
|
49
|
+
),
|
|
50
|
+
observation_id= observation.id, #type: ignore
|
|
51
|
+
data=operation,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
if operation_name == "AddField":
|
|
55
|
+
typer.echo(
|
|
56
|
+
f"Generating finding: "
|
|
57
|
+
f"observation={observation.id}, "
|
|
58
|
+
f"data={operation}"
|
|
59
|
+
)
|
|
60
|
+
findings.append(
|
|
61
|
+
Findings(
|
|
62
|
+
analyzer="django-migrations",
|
|
63
|
+
severity="info",
|
|
64
|
+
title="Field added",
|
|
65
|
+
message=(
|
|
66
|
+
"A database field is being "
|
|
67
|
+
"added by this migration."
|
|
68
|
+
),
|
|
69
|
+
observation_id= observation.id, #type: ignore
|
|
70
|
+
data=operation,
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return findings
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from chronicle.storage.models import Observation,Findings
|
|
4
|
+
from .base import BaseAnalyzer
|
|
5
|
+
from chronicle.analysis.context import AnalysisContext
|
|
6
|
+
|
|
7
|
+
class GitAnalyzer(BaseAnalyzer):
|
|
8
|
+
def __init__(self, project_root: Path) -> None:
|
|
9
|
+
super().__init__(project_root)
|
|
10
|
+
|
|
11
|
+
def analyze(self,context:AnalysisContext) -> list[Findings]:
|
|
12
|
+
findings = []
|
|
13
|
+
for observation in context.observations:
|
|
14
|
+
if observation.type != "commit":
|
|
15
|
+
continue
|
|
16
|
+
if observation.source != "git":
|
|
17
|
+
continue
|
|
18
|
+
|
|
19
|
+
message = observation.data.get("message","Not given")
|
|
20
|
+
author = observation.data.get("author","unknown")
|
|
21
|
+
parsed_message = author + ":" + message
|
|
22
|
+
changes = observation.data.get("changes",[])
|
|
23
|
+
|
|
24
|
+
for change in changes:
|
|
25
|
+
file_status = change.get("status")
|
|
26
|
+
if file_status == "A":
|
|
27
|
+
findings.append(
|
|
28
|
+
Findings(
|
|
29
|
+
analyzer="git_analyzer",
|
|
30
|
+
severity="info",
|
|
31
|
+
title="New File created",
|
|
32
|
+
message=parsed_message,
|
|
33
|
+
observation_id=observation.id, #type: ignore
|
|
34
|
+
data=change
|
|
35
|
+
))
|
|
36
|
+
|
|
37
|
+
return findings
|