work-chronicler 0.1.0
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.
- package/README.md +432 -0
- package/bin/mcp.js +6 -0
- package/bin/work-chronicler.js +3 -0
- package/dist/cli/analyzer/classifier.d.ts +31 -0
- package/dist/cli/analyzer/classifier.d.ts.map +1 -0
- package/dist/cli/analyzer/classifier.js +171 -0
- package/dist/cli/analyzer/index.d.ts +5 -0
- package/dist/cli/analyzer/index.d.ts.map +1 -0
- package/dist/cli/analyzer/index.js +4 -0
- package/dist/cli/analyzer/projects.d.ts +10 -0
- package/dist/cli/analyzer/projects.d.ts.map +1 -0
- package/dist/cli/analyzer/projects.js +228 -0
- package/dist/cli/analyzer/stats.d.ts +30 -0
- package/dist/cli/analyzer/stats.d.ts.map +1 -0
- package/dist/cli/analyzer/stats.js +80 -0
- package/dist/cli/analyzer/timeline.d.ts +6 -0
- package/dist/cli/analyzer/timeline.d.ts.map +1 -0
- package/dist/cli/analyzer/timeline.js +224 -0
- package/dist/cli/commands/analyze.d.ts +3 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +216 -0
- package/dist/cli/commands/fetch/all.d.ts +3 -0
- package/dist/cli/commands/fetch/all.d.ts.map +1 -0
- package/dist/cli/commands/fetch/all.js +91 -0
- package/dist/cli/commands/fetch/github.d.ts +3 -0
- package/dist/cli/commands/fetch/github.d.ts.map +1 -0
- package/dist/cli/commands/fetch/github.js +39 -0
- package/dist/cli/commands/fetch/jira.d.ts +3 -0
- package/dist/cli/commands/fetch/jira.d.ts.map +1 -0
- package/dist/cli/commands/fetch/jira.js +39 -0
- package/dist/cli/commands/filter.d.ts +3 -0
- package/dist/cli/commands/filter.d.ts.map +1 -0
- package/dist/cli/commands/filter.js +247 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +47 -0
- package/dist/cli/commands/link.d.ts +3 -0
- package/dist/cli/commands/link.d.ts.map +1 -0
- package/dist/cli/commands/link.js +25 -0
- package/dist/cli/commands/mcp.d.ts +3 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +43 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +28 -0
- package/dist/cli/fetchers/github.d.ts +20 -0
- package/dist/cli/fetchers/github.d.ts.map +1 -0
- package/dist/cli/fetchers/github.js +345 -0
- package/dist/cli/fetchers/jira.d.ts +20 -0
- package/dist/cli/fetchers/jira.d.ts.map +1 -0
- package/dist/cli/fetchers/jira.js +268 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +43 -0
- package/dist/cli/linker/index.d.ts +17 -0
- package/dist/cli/linker/index.d.ts.map +1 -0
- package/dist/cli/linker/index.js +129 -0
- package/dist/cli/prompts/index.d.ts +61 -0
- package/dist/cli/prompts/index.d.ts.map +1 -0
- package/dist/cli/prompts/index.js +258 -0
- package/dist/core/config/loader.d.ts +61 -0
- package/dist/core/config/loader.d.ts.map +1 -0
- package/dist/core/config/loader.js +146 -0
- package/dist/core/config/schema.d.ts +587 -0
- package/dist/core/config/schema.d.ts.map +1 -0
- package/dist/core/config/schema.js +95 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/storage/index.d.ts +4 -0
- package/dist/core/storage/index.d.ts.map +1 -0
- package/dist/core/storage/index.js +3 -0
- package/dist/core/storage/paths.d.ts +85 -0
- package/dist/core/storage/paths.d.ts.map +1 -0
- package/dist/core/storage/paths.js +110 -0
- package/dist/core/storage/reader.d.ts +69 -0
- package/dist/core/storage/reader.d.ts.map +1 -0
- package/dist/core/storage/reader.js +181 -0
- package/dist/core/storage/writer.d.ts +41 -0
- package/dist/core/storage/writer.d.ts.map +1 -0
- package/dist/core/storage/writer.js +50 -0
- package/dist/core/types/index.d.ts +5 -0
- package/dist/core/types/index.d.ts.map +1 -0
- package/dist/core/types/index.js +4 -0
- package/dist/core/types/pr.d.ts +75 -0
- package/dist/core/types/pr.d.ts.map +1 -0
- package/dist/core/types/pr.js +35 -0
- package/dist/core/types/project.d.ts +450 -0
- package/dist/core/types/project.d.ts.map +1 -0
- package/dist/core/types/project.js +75 -0
- package/dist/core/types/ticket.d.ts +51 -0
- package/dist/core/types/ticket.d.ts.map +1 -0
- package/dist/core/types/ticket.js +17 -0
- package/dist/core/types/timeline.d.ts +1177 -0
- package/dist/core/types/timeline.d.ts.map +1 -0
- package/dist/core/types/timeline.js +100 -0
- package/dist/mcp/index.d.ts +15 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +26 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +584 -0
- package/dist/mcp/tools/get-stats.d.ts +26 -0
- package/dist/mcp/tools/get-stats.d.ts.map +1 -0
- package/dist/mcp/tools/get-stats.js +64 -0
- package/dist/mcp/tools/search-prs.d.ts +18 -0
- package/dist/mcp/tools/search-prs.d.ts.map +1 -0
- package/dist/mcp/tools/search-prs.js +44 -0
- package/dist/mcp/tools/search-tickets.d.ts +19 -0
- package/dist/mcp/tools/search-tickets.d.ts.map +1 -0
- package/dist/mcp/tools/search-tickets.js +49 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# work-chronicler
|
|
2
|
+
|
|
3
|
+
Gather, analyze, and summarize your work history from GitHub PRs, JIRA tickets and other documents for performance reviews, resumes or self-evaluation.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
Performance review time rolls around and you need to:
|
|
8
|
+
- Remember everything you accomplished in the past year
|
|
9
|
+
- Write compelling bullet points for your self-review
|
|
10
|
+
- Update your resume with recent achievements
|
|
11
|
+
|
|
12
|
+
But you didn't take notes, and now you're scrolling through months of PRs trying to piece together what you did.
|
|
13
|
+
|
|
14
|
+
## The Solution
|
|
15
|
+
|
|
16
|
+
`work-chronicler` fetches your PR descriptions and JIRA tickets, stores them locally as searchable markdown files, and provides AI-ready tooling to analyze and summarize your work.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Local Development (this repo)
|
|
21
|
+
|
|
22
|
+
Use `pnpm cli` instead of `work-chronicler`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm install
|
|
26
|
+
pnpm cli init
|
|
27
|
+
pnpm cli fetch:all
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Published Package Usage
|
|
31
|
+
|
|
32
|
+
After the package is published to npm:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Install globally
|
|
36
|
+
npm install -g work-chronicler
|
|
37
|
+
|
|
38
|
+
# Create a dedicated directory for your work history
|
|
39
|
+
mkdir ~/work-history
|
|
40
|
+
cd ~/work-history
|
|
41
|
+
|
|
42
|
+
# Initialize - creates .env and work-chronicler.yaml in current directory
|
|
43
|
+
work-chronicler init
|
|
44
|
+
|
|
45
|
+
# Edit .env with your tokens:
|
|
46
|
+
# GITHUB_TOKEN=ghp_xxxx
|
|
47
|
+
# JIRA_EMAIL=you@company.com
|
|
48
|
+
# JIRA_TOKEN=xxxx
|
|
49
|
+
|
|
50
|
+
# Edit work-chronicler.yaml with your GitHub username, orgs, and JIRA config
|
|
51
|
+
|
|
52
|
+
# Fetch your work history
|
|
53
|
+
work-chronicler fetch:all
|
|
54
|
+
|
|
55
|
+
# Analyze and generate stats
|
|
56
|
+
work-chronicler analyze --projects --timeline
|
|
57
|
+
|
|
58
|
+
# Check what you have
|
|
59
|
+
work-chronicler status
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
All data is stored in the current directory under `work-log/`. You can version control this directory or keep it private.
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
All commands operate on the current directory. The CLI looks for `work-chronicler.yaml` in the current directory first, then falls back to `~/.config/work-chronicler/config.yaml`.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Create config files (.env and work-chronicler.yaml)
|
|
70
|
+
work-chronicler init
|
|
71
|
+
|
|
72
|
+
# Fetch from GitHub and JIRA
|
|
73
|
+
work-chronicler fetch:all
|
|
74
|
+
|
|
75
|
+
# Cross-reference PRs ↔ tickets
|
|
76
|
+
work-chronicler link
|
|
77
|
+
|
|
78
|
+
# Generate analysis files
|
|
79
|
+
work-chronicler analyze --projects --timeline
|
|
80
|
+
|
|
81
|
+
# Check status
|
|
82
|
+
work-chronicler status
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> **Note**: For local development in this repo, prefix all commands with `pnpm cli` (e.g., `pnpm cli init`).
|
|
86
|
+
|
|
87
|
+
## Directory Structure
|
|
88
|
+
|
|
89
|
+
After fetching, your data is organized like this:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
work-log/
|
|
93
|
+
├── pull-requests/
|
|
94
|
+
│ └── <org-name>/
|
|
95
|
+
│ └── <repo-name>/
|
|
96
|
+
│ ├── 2024-01-15_123.md
|
|
97
|
+
│ └── 2024-02-20_456.md
|
|
98
|
+
├── jira/
|
|
99
|
+
│ └── <org-name>/
|
|
100
|
+
│ └── <project-key>/
|
|
101
|
+
│ ├── PROJ-100.md
|
|
102
|
+
│ └── PROJ-101.md
|
|
103
|
+
├── performance-reviews/ # Add your own review docs here
|
|
104
|
+
├── .analysis/ # Generated analysis
|
|
105
|
+
│ ├── stats.json # Impact breakdown, repo stats, etc.
|
|
106
|
+
│ ├── projects.json # Detected project groupings
|
|
107
|
+
│ └── timeline.json # Chronological view by week/month
|
|
108
|
+
└── filtered/ # Filtered subset (from filter command)
|
|
109
|
+
├── pull-requests/
|
|
110
|
+
├── jira/
|
|
111
|
+
└── .analysis/
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## CLI Commands
|
|
115
|
+
|
|
116
|
+
| Command | Description |
|
|
117
|
+
|---------|-------------|
|
|
118
|
+
| `init` | Create a new config file |
|
|
119
|
+
| `fetch:github` | Fetch PRs from GitHub |
|
|
120
|
+
| `fetch:jira` | Fetch tickets from JIRA |
|
|
121
|
+
| `fetch:all` | Fetch both PRs and JIRA tickets |
|
|
122
|
+
| `link` | Cross-reference PRs and JIRA tickets |
|
|
123
|
+
| `analyze` | Classify PRs by impact and generate stats |
|
|
124
|
+
| `filter` | Filter work-log to a subset based on criteria |
|
|
125
|
+
| `status` | Show current state of fetched data |
|
|
126
|
+
| `mcp` | Start the MCP server for AI assistant integration |
|
|
127
|
+
|
|
128
|
+
### Analyze Command
|
|
129
|
+
|
|
130
|
+
Classifies PRs into four impact tiers and generates statistics:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Interactive mode - prompts what to generate (tag-prs, projects, timeline)
|
|
134
|
+
work-chronicler analyze
|
|
135
|
+
|
|
136
|
+
# Run all analysis at once
|
|
137
|
+
work-chronicler analyze --all
|
|
138
|
+
|
|
139
|
+
# Or run specific analysis:
|
|
140
|
+
work-chronicler analyze --tag-prs # Tag PRs with impact levels
|
|
141
|
+
work-chronicler analyze --projects # Detect project groupings
|
|
142
|
+
work-chronicler analyze --timeline # Generate chronological timeline
|
|
143
|
+
|
|
144
|
+
# Analyze full work-log even if filtered/ exists
|
|
145
|
+
work-chronicler analyze --full
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Impact Tiers:**
|
|
149
|
+
- **flagship**: Large initiatives (500+ lines or 15+ files), migrations, platform changes
|
|
150
|
+
- **major**: Significant features (200+ lines or 8+ files), `feat:` or `refactor:` commits
|
|
151
|
+
- **standard**: Regular work, bug fixes, `fix:` or `test:` commits
|
|
152
|
+
- **minor**: Small changes (<20 lines), docs, chores, dependency updates
|
|
153
|
+
|
|
154
|
+
**Project Detection:**
|
|
155
|
+
|
|
156
|
+
The `--projects` flag groups related PRs and tickets into projects based on shared JIRA ticket references. PRs that reference the same ticket are grouped together as a project. PRs without ticket references remain unassigned.
|
|
157
|
+
|
|
158
|
+
Output is written to `.analysis/projects.json`.
|
|
159
|
+
|
|
160
|
+
**Timeline View:**
|
|
161
|
+
|
|
162
|
+
The `--timeline` flag generates a chronological view of your work:
|
|
163
|
+
- Groups PRs and tickets by ISO week and month
|
|
164
|
+
- Shows weekly/monthly stats (PR count, ticket count, additions/deletions)
|
|
165
|
+
- Identifies busiest week and month
|
|
166
|
+
- Tracks impact distribution over time
|
|
167
|
+
|
|
168
|
+
Output is written to `.analysis/timeline.json`.
|
|
169
|
+
|
|
170
|
+
### Filter Command
|
|
171
|
+
|
|
172
|
+
Create a filtered subset of your work-log:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Interactive mode (prompts for filter options)
|
|
176
|
+
work-chronicler filter
|
|
177
|
+
|
|
178
|
+
# Filter by organization (useful for separating work vs personal)
|
|
179
|
+
work-chronicler filter --org my-work-org
|
|
180
|
+
work-chronicler filter --exclude-org personal-github
|
|
181
|
+
|
|
182
|
+
# Filter by repository
|
|
183
|
+
work-chronicler filter --repo my-org/important-repo
|
|
184
|
+
work-chronicler filter --exclude-repo my-org/experimental-repo
|
|
185
|
+
|
|
186
|
+
# Exclude minor PRs
|
|
187
|
+
work-chronicler filter --exclude-impact minor
|
|
188
|
+
|
|
189
|
+
# Only major+ merged PRs
|
|
190
|
+
work-chronicler filter --min-impact major --merged-only
|
|
191
|
+
|
|
192
|
+
# Only PRs linked to tickets with 100+ lines changed
|
|
193
|
+
work-chronicler filter --linked-only --min-loc 100
|
|
194
|
+
|
|
195
|
+
# Combine filters (e.g., work PRs that are major+)
|
|
196
|
+
work-chronicler filter --org my-work-org --min-impact major
|
|
197
|
+
|
|
198
|
+
# Clear filtered data
|
|
199
|
+
work-chronicler filter --clear
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Filtered files are written to `work-log/filtered/` with their own analysis (stats, projects, timeline).
|
|
203
|
+
|
|
204
|
+
### Interactive Prompts
|
|
205
|
+
|
|
206
|
+
Most commands will prompt for options when run without flags:
|
|
207
|
+
|
|
208
|
+
- **fetch:github/jira/all** - Prompts whether to use cache mode if data already exists
|
|
209
|
+
- **analyze** - Prompts what to generate (tag-prs, projects, timeline) and whether to use filtered data
|
|
210
|
+
- **filter** - Prompts for all filter options
|
|
211
|
+
|
|
212
|
+
Use flags like `--cache`, `--all`, `--full`, etc. to skip prompts in scripts.
|
|
213
|
+
|
|
214
|
+
## Configuration
|
|
215
|
+
|
|
216
|
+
See [work-chronicler.example.yaml](work-chronicler.example.yaml) for a complete example.
|
|
217
|
+
|
|
218
|
+
### GitHub Token
|
|
219
|
+
|
|
220
|
+
Create a personal access token at https://github.com/settings/tokens with the `repo` scope (or `public_repo` for public repos only).
|
|
221
|
+
|
|
222
|
+
### JIRA Token
|
|
223
|
+
|
|
224
|
+
Create an API token at https://id.atlassian.com/manage-profile/security/api-tokens
|
|
225
|
+
|
|
226
|
+
## MCP Server
|
|
227
|
+
|
|
228
|
+
work-chronicler includes an MCP (Model Context Protocol) server that exposes your work history to AI assistants like Claude Desktop and Cursor.
|
|
229
|
+
|
|
230
|
+
### Setup
|
|
231
|
+
|
|
232
|
+
1. First, fetch and analyze your data:
|
|
233
|
+
```bash
|
|
234
|
+
work-chronicler fetch:all
|
|
235
|
+
work-chronicler analyze --projects --timeline
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
2. Configure your AI assistant.
|
|
239
|
+
|
|
240
|
+
The MCP server needs to find your `work-chronicler.yaml` config. Use the `WORK_CHRONICLER_DIR` environment variable to point to your work history directory.
|
|
241
|
+
|
|
242
|
+
**Published Package** (installed via npm):
|
|
243
|
+
|
|
244
|
+
Use `npx work-chronicler mcp` which runs through the CLI. This is the recommended approach for installed packages.
|
|
245
|
+
|
|
246
|
+
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"mcpServers": {
|
|
250
|
+
"work-chronicler": {
|
|
251
|
+
"command": "npx",
|
|
252
|
+
"args": ["work-chronicler", "mcp"],
|
|
253
|
+
"env": {
|
|
254
|
+
"WORK_CHRONICLER_DIR": "/Users/you/work-history"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Cursor** (`.cursor/mcp.json` in your project):
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"mcpServers": {
|
|
265
|
+
"work-chronicler": {
|
|
266
|
+
"command": "npx",
|
|
267
|
+
"args": ["work-chronicler", "mcp"],
|
|
268
|
+
"env": {
|
|
269
|
+
"WORK_CHRONICLER_DIR": "/Users/you/work-history"
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Local Development** (running from source):
|
|
277
|
+
|
|
278
|
+
For local development, use `node bin/mcp.js` which starts the MCP server directly without CLI overhead. This is faster for development iteration.
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"mcpServers": {
|
|
283
|
+
"work-chronicler": {
|
|
284
|
+
"command": "node",
|
|
285
|
+
"args": ["/path/to/work-chronicler/bin/mcp.js"],
|
|
286
|
+
"env": {
|
|
287
|
+
"WORK_CHRONICLER_DIR": "/path/to/work-chronicler"
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
> **Note:** Replace `/path/to/work-chronicler` with your actual project path. Run `pnpm build` first to generate the dist files.
|
|
295
|
+
|
|
296
|
+
Alternatively, you can use the CLI approach for local development too:
|
|
297
|
+
```json
|
|
298
|
+
{
|
|
299
|
+
"mcpServers": {
|
|
300
|
+
"work-chronicler": {
|
|
301
|
+
"command": "node",
|
|
302
|
+
"args": ["/path/to/work-chronicler/bin/work-chronicler.js", "mcp"],
|
|
303
|
+
"env": {
|
|
304
|
+
"WORK_CHRONICLER_DIR": "/path/to/work-chronicler"
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
3. Restart your AI assistant to load the MCP server.
|
|
312
|
+
|
|
313
|
+
**Environment Variables:**
|
|
314
|
+
- `WORK_CHRONICLER_DIR` - Directory containing your `work-chronicler.yaml`
|
|
315
|
+
- `WORK_CHRONICLER_CONFIG` - Full path to config file (alternative to DIR)
|
|
316
|
+
|
|
317
|
+
### Available Tools
|
|
318
|
+
|
|
319
|
+
| Tool | Description |
|
|
320
|
+
|------|-------------|
|
|
321
|
+
| `search_prs` | Search PRs by date range, repo, keywords, impact level, or state |
|
|
322
|
+
| `search_tickets` | Search JIRA tickets by project, status, or keywords |
|
|
323
|
+
| `get_linked_work` | Get a PR with its linked JIRA tickets (or vice versa) |
|
|
324
|
+
| `list_repos` | List all repositories with statistics |
|
|
325
|
+
| `get_stats` | Get summary statistics (reads from stats.json or computes on-the-fly) |
|
|
326
|
+
| `get_projects` | Get detected project groupings with confidence levels |
|
|
327
|
+
| `get_timeline` | Get chronological timeline of work grouped by week or month |
|
|
328
|
+
|
|
329
|
+
### Example Prompts
|
|
330
|
+
|
|
331
|
+
Once configured, you can ask your AI assistant:
|
|
332
|
+
|
|
333
|
+
- "Show me my flagship PRs from last quarter"
|
|
334
|
+
- "What projects did I work on with high confidence groupings?"
|
|
335
|
+
- "Find all my work related to authentication"
|
|
336
|
+
- "Summarize my work from January to March"
|
|
337
|
+
- "What was my busiest week?"
|
|
338
|
+
|
|
339
|
+
### CLI Commands
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
# Show MCP server info
|
|
343
|
+
work-chronicler mcp --info
|
|
344
|
+
|
|
345
|
+
# Start MCP server (stdio transport)
|
|
346
|
+
work-chronicler mcp
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Project Structure
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
work-chronicler/
|
|
353
|
+
├── src/
|
|
354
|
+
│ ├── cli/ # CLI application (Commander)
|
|
355
|
+
│ │ ├── commands/ # CLI commands
|
|
356
|
+
│ │ ├── fetchers/ # GitHub and JIRA fetchers
|
|
357
|
+
│ │ ├── linker/ # Cross-reference linking
|
|
358
|
+
│ │ ├── analyzer/ # Impact analysis
|
|
359
|
+
│ │ └── prompts/ # Interactive prompts
|
|
360
|
+
│ ├── mcp/ # MCP server for AI assistants
|
|
361
|
+
│ └── core/ # Shared types, config, storage
|
|
362
|
+
│ ├── config/ # Config loading and schema
|
|
363
|
+
│ ├── storage/ # Markdown file reader/writer
|
|
364
|
+
│ └── types/ # Zod schemas and types
|
|
365
|
+
├── bin/ # CLI entry point
|
|
366
|
+
└── tools/
|
|
367
|
+
└── git-hooks/ # Git hook management
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Development
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
# Install dependencies
|
|
374
|
+
pnpm install
|
|
375
|
+
|
|
376
|
+
# Build all packages
|
|
377
|
+
pnpm build
|
|
378
|
+
|
|
379
|
+
# Run type checking
|
|
380
|
+
pnpm type-check
|
|
381
|
+
|
|
382
|
+
# Run linting
|
|
383
|
+
pnpm lint
|
|
384
|
+
|
|
385
|
+
# Run tests
|
|
386
|
+
pnpm test
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Publishing
|
|
390
|
+
|
|
391
|
+
The package is published to npm as `work-chronicler`.
|
|
392
|
+
|
|
393
|
+
### Release Process
|
|
394
|
+
|
|
395
|
+
1. Update the version in `package.json`
|
|
396
|
+
2. Commit and push to main
|
|
397
|
+
3. Create and push a version tag:
|
|
398
|
+
```bash
|
|
399
|
+
git tag v0.1.0
|
|
400
|
+
git push origin v0.1.0
|
|
401
|
+
```
|
|
402
|
+
4. The GitHub Action will automatically publish to npm
|
|
403
|
+
|
|
404
|
+
### Manual Publishing
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
# Build the package
|
|
408
|
+
pnpm build
|
|
409
|
+
|
|
410
|
+
# Dry run to verify what will be published
|
|
411
|
+
pnpm publish --dry-run --no-git-checks
|
|
412
|
+
|
|
413
|
+
# Publish (requires NPM_TOKEN or npm login)
|
|
414
|
+
pnpm publish --access public --no-git-checks
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Roadmap
|
|
418
|
+
|
|
419
|
+
- [x] **Analysis commands**: Categorize work by size/impact (4-tier classification)
|
|
420
|
+
- [x] **Project detection**: Group related PRs/tickets into initiatives
|
|
421
|
+
- [x] **Timeline view**: Chronological view of work grouped by week/month
|
|
422
|
+
- [x] **MCP server**: Full implementation for AI assistant integration
|
|
423
|
+
- [ ] **AI summarization**: Claude/Cursor commands + CLI commands for summaries
|
|
424
|
+
- [ ] **Supporting documents**: Import past reviews, resumes, notes for context
|
|
425
|
+
- [ ] **Google Docs integration**: Import performance review docs
|
|
426
|
+
- [ ] **Linear support**: Alternative to JIRA
|
|
427
|
+
- [ ] **Notion integration**: Import from Notion
|
|
428
|
+
- [ ] **Incremental sync**: Only fetch new/updated items
|
|
429
|
+
|
|
430
|
+
## License
|
|
431
|
+
|
|
432
|
+
MIT
|
package/bin/mcp.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AnalysisConfig, PRImpact, PullRequest } from '../../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default thresholds for impact classification
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_THRESHOLDS: {
|
|
6
|
+
minor: {
|
|
7
|
+
maxLines: number;
|
|
8
|
+
maxFiles: number;
|
|
9
|
+
};
|
|
10
|
+
major: {
|
|
11
|
+
minLines: number;
|
|
12
|
+
minFiles: number;
|
|
13
|
+
};
|
|
14
|
+
flagship: {
|
|
15
|
+
minLines: number;
|
|
16
|
+
minFiles: number;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Classify a PR's impact level based on its characteristics
|
|
21
|
+
*/
|
|
22
|
+
export declare function classifyPRImpact(pr: PullRequest, config?: AnalysisConfig): PRImpact;
|
|
23
|
+
/**
|
|
24
|
+
* Get a human-readable description of what the impact means
|
|
25
|
+
*/
|
|
26
|
+
export declare function getImpactDescription(impact: PRImpact): string;
|
|
27
|
+
/**
|
|
28
|
+
* Impact level hierarchy for comparison (higher = more significant)
|
|
29
|
+
*/
|
|
30
|
+
export declare const IMPACT_HIERARCHY: Record<PRImpact, number>;
|
|
31
|
+
//# sourceMappingURL=classifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../../../src/cli/analyzer/classifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEzE;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;CAI9B,CAAC;AAkFF;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,WAAW,EACf,MAAM,CAAC,EAAE,cAAc,GACtB,QAAQ,CAiFV;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAe7D;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAKrD,CAAC"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default thresholds for impact classification
|
|
3
|
+
*/
|
|
4
|
+
export const DEFAULT_THRESHOLDS = {
|
|
5
|
+
minor: { maxLines: 20, maxFiles: 3 },
|
|
6
|
+
major: { minLines: 200, minFiles: 8 },
|
|
7
|
+
flagship: { minLines: 500, minFiles: 15 },
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Conventional commit prefixes and their default impact
|
|
11
|
+
*/
|
|
12
|
+
const CONVENTIONAL_COMMIT_MAP = {
|
|
13
|
+
// Minor by default (can be upgraded by size)
|
|
14
|
+
chore: 'minor',
|
|
15
|
+
docs: 'minor',
|
|
16
|
+
style: 'minor',
|
|
17
|
+
ci: 'minor',
|
|
18
|
+
build: 'minor',
|
|
19
|
+
// Standard by default
|
|
20
|
+
fix: 'standard',
|
|
21
|
+
test: 'standard',
|
|
22
|
+
perf: 'standard',
|
|
23
|
+
// Major by default (can be upgraded to flagship by size)
|
|
24
|
+
feat: 'major',
|
|
25
|
+
refactor: 'major',
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Patterns that indicate minor/low-value PRs (checked after conventional commits)
|
|
29
|
+
*/
|
|
30
|
+
const MINOR_PATTERNS = [
|
|
31
|
+
/^fix\s*typo/i,
|
|
32
|
+
/^typo/i,
|
|
33
|
+
/^update\s+deps?/i,
|
|
34
|
+
/^bump\s+/i,
|
|
35
|
+
/^\[chore\]/i,
|
|
36
|
+
/^update\s+dependencies/i,
|
|
37
|
+
/^dependency\s+update/i,
|
|
38
|
+
/^renovate/i,
|
|
39
|
+
/^dependabot/i,
|
|
40
|
+
];
|
|
41
|
+
const MINOR_LABELS = [
|
|
42
|
+
'dependencies',
|
|
43
|
+
'chore',
|
|
44
|
+
'maintenance',
|
|
45
|
+
'renovate',
|
|
46
|
+
'dependabot',
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Patterns that indicate major work (checked after conventional commits)
|
|
50
|
+
*/
|
|
51
|
+
const MAJOR_PATTERNS = [/^\[feat\]/i, /^add\s+/i, /^implement\s+/i, /^new\s+/i];
|
|
52
|
+
/**
|
|
53
|
+
* Patterns that indicate flagship/breaking work
|
|
54
|
+
*/
|
|
55
|
+
const FLAGSHIP_PATTERNS = [
|
|
56
|
+
/breaking/i,
|
|
57
|
+
/platform/i,
|
|
58
|
+
/architect/i,
|
|
59
|
+
/migration/i,
|
|
60
|
+
/redesign/i,
|
|
61
|
+
/overhaul/i,
|
|
62
|
+
/rewrite/i,
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* Extract conventional commit type from PR title
|
|
66
|
+
* Handles: "feat: message", "feat(scope): message", "[feat] message"
|
|
67
|
+
*/
|
|
68
|
+
function extractConventionalType(title) {
|
|
69
|
+
// Match "type:" or "type(scope):"
|
|
70
|
+
const conventionalMatch = title.match(/^(\w+)(?:\([^)]*\))?:\s*/i);
|
|
71
|
+
if (conventionalMatch?.[1]) {
|
|
72
|
+
return conventionalMatch[1].toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
// Match "[type]"
|
|
75
|
+
const bracketMatch = title.match(/^\[(\w+)\]/i);
|
|
76
|
+
if (bracketMatch?.[1]) {
|
|
77
|
+
return bracketMatch[1].toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Classify a PR's impact level based on its characteristics
|
|
83
|
+
*/
|
|
84
|
+
export function classifyPRImpact(pr, config) {
|
|
85
|
+
const thresholds = {
|
|
86
|
+
minor: { ...DEFAULT_THRESHOLDS.minor, ...config?.thresholds?.minor },
|
|
87
|
+
major: { ...DEFAULT_THRESHOLDS.major, ...config?.thresholds?.major },
|
|
88
|
+
flagship: {
|
|
89
|
+
...DEFAULT_THRESHOLDS.flagship,
|
|
90
|
+
...config?.thresholds?.flagship,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
const totalLines = pr.additions + pr.deletions;
|
|
94
|
+
const title = pr.title;
|
|
95
|
+
// Check size thresholds first for flagship
|
|
96
|
+
const isFlagshipSize = (thresholds.flagship.minLines &&
|
|
97
|
+
totalLines >= thresholds.flagship.minLines) ||
|
|
98
|
+
(thresholds.flagship.minFiles &&
|
|
99
|
+
pr.changedFiles >= thresholds.flagship.minFiles);
|
|
100
|
+
// Check flagship patterns
|
|
101
|
+
const hasFlagshipPattern = FLAGSHIP_PATTERNS.some((p) => p.test(title));
|
|
102
|
+
if (isFlagshipSize || hasFlagshipPattern) {
|
|
103
|
+
return 'flagship';
|
|
104
|
+
}
|
|
105
|
+
// Check conventional commit type
|
|
106
|
+
const conventionalType = extractConventionalType(title);
|
|
107
|
+
let baseImpact = null;
|
|
108
|
+
if (conventionalType && conventionalType in CONVENTIONAL_COMMIT_MAP) {
|
|
109
|
+
baseImpact = CONVENTIONAL_COMMIT_MAP[conventionalType] ?? null;
|
|
110
|
+
}
|
|
111
|
+
// Check for minor patterns and labels
|
|
112
|
+
const hasMinorPattern = MINOR_PATTERNS.some((p) => p.test(title));
|
|
113
|
+
const hasMinorLabel = pr.labels.some((label) => MINOR_LABELS.includes(label.toLowerCase()));
|
|
114
|
+
// Check size for minor
|
|
115
|
+
const isMinorSize = thresholds.minor.maxLines &&
|
|
116
|
+
totalLines <= thresholds.minor.maxLines &&
|
|
117
|
+
thresholds.minor.maxFiles &&
|
|
118
|
+
pr.changedFiles <= thresholds.minor.maxFiles;
|
|
119
|
+
// If conventional commit says minor, or patterns say minor with small size
|
|
120
|
+
if (baseImpact === 'minor' ||
|
|
121
|
+
((hasMinorPattern || hasMinorLabel) && isMinorSize)) {
|
|
122
|
+
return 'minor';
|
|
123
|
+
}
|
|
124
|
+
// Very small changes are minor regardless
|
|
125
|
+
if (totalLines <= 10 && pr.changedFiles <= 2) {
|
|
126
|
+
return 'minor';
|
|
127
|
+
}
|
|
128
|
+
// Check for major size
|
|
129
|
+
const isMajorSize = (thresholds.major.minLines && totalLines >= thresholds.major.minLines) ||
|
|
130
|
+
(thresholds.major.minFiles && pr.changedFiles >= thresholds.major.minFiles);
|
|
131
|
+
// Check for major patterns
|
|
132
|
+
const hasMajorPattern = MAJOR_PATTERNS.some((p) => p.test(title));
|
|
133
|
+
// If conventional commit says major, or patterns/size indicate major
|
|
134
|
+
if (baseImpact === 'major' || isMajorSize || hasMajorPattern) {
|
|
135
|
+
return 'major';
|
|
136
|
+
}
|
|
137
|
+
// If conventional commit gave us a base impact, use it
|
|
138
|
+
if (baseImpact) {
|
|
139
|
+
return baseImpact;
|
|
140
|
+
}
|
|
141
|
+
// Everything else is standard
|
|
142
|
+
return 'standard';
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get a human-readable description of what the impact means
|
|
146
|
+
*/
|
|
147
|
+
export function getImpactDescription(impact) {
|
|
148
|
+
switch (impact) {
|
|
149
|
+
case 'flagship':
|
|
150
|
+
return 'Large initiatives, platform changes, or multi-PR efforts';
|
|
151
|
+
case 'major':
|
|
152
|
+
return 'Significant features, larger refactors, or architectural work';
|
|
153
|
+
case 'standard':
|
|
154
|
+
return 'Regular feature work, bug fixes, and moderate changes';
|
|
155
|
+
case 'minor':
|
|
156
|
+
return 'Small fixes, typos, docs, or dependency updates';
|
|
157
|
+
default: {
|
|
158
|
+
const _exhaustive = impact;
|
|
159
|
+
return _exhaustive;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Impact level hierarchy for comparison (higher = more significant)
|
|
165
|
+
*/
|
|
166
|
+
export const IMPACT_HIERARCHY = {
|
|
167
|
+
minor: 0,
|
|
168
|
+
standard: 1,
|
|
169
|
+
major: 2,
|
|
170
|
+
flagship: 3,
|
|
171
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { classifyPRImpact, DEFAULT_THRESHOLDS, getImpactDescription, IMPACT_HIERARCHY, } from './classifier.js';
|
|
2
|
+
export { detectProjects } from './projects.js';
|
|
3
|
+
export { type AnalysisStats, generateStats } from './stats.js';
|
|
4
|
+
export { generateTimeline } from './timeline.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/analyzer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,KAAK,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { JiraTicketFile, ProjectsAnalysis, PullRequestFile } from '../../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detect project groupings from PRs and tickets
|
|
4
|
+
*
|
|
5
|
+
* Projects are detected based on shared JIRA ticket references.
|
|
6
|
+
* PRs that reference the same ticket are grouped together.
|
|
7
|
+
* PRs without ticket references are not grouped into projects.
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectProjects(prs: PullRequestFile[], tickets: JiraTicketFile[], since: string, until: string): ProjectsAnalysis;
|
|
10
|
+
//# sourceMappingURL=projects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../../src/cli/analyzer/projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAGd,gBAAgB,EAChB,eAAe,EAChB,MAAM,aAAa,CAAC;AAiFrB;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,eAAe,EAAE,EACtB,OAAO,EAAE,cAAc,EAAE,EACzB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,gBAAgB,CA0LlB"}
|