@vanshbhardwaj/worklog 1.0.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.
Files changed (68) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +28 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  4. package/.github/workflows/release.yml +63 -0
  5. package/.github/workflows/validate.yml +103 -0
  6. package/.prettierrc +8 -0
  7. package/ARCHITECTURE.md +82 -0
  8. package/CODE_OF_CONDUCT.md +65 -0
  9. package/CONTRIBUTING.md +74 -0
  10. package/INSTALLATION.md +98 -0
  11. package/LICENSE +21 -0
  12. package/README.md +246 -0
  13. package/SECURITY.md +18 -0
  14. package/benchmarks/benchmark.ts +149 -0
  15. package/benchmarks/vitest.bench.config.ts +9 -0
  16. package/dist/database/migrations/001_initial.sql +60 -0
  17. package/dist/database/migrations/002_relational_tables.sql +22 -0
  18. package/dist/index.cjs +31007 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +1873 -0
  22. package/dist/index.js.map +1 -0
  23. package/eslint.config.js +38 -0
  24. package/package.json +49 -0
  25. package/pnpm-workspace.yaml +6 -0
  26. package/scripts/build-bin.js +75 -0
  27. package/sea-config.json +5 -0
  28. package/src/cli/commands/add.ts +176 -0
  29. package/src/cli/commands/continue.ts +80 -0
  30. package/src/cli/commands/dashboard.ts +30 -0
  31. package/src/cli/commands/export.ts +72 -0
  32. package/src/cli/commands/init.ts +52 -0
  33. package/src/cli/commands/list.ts +33 -0
  34. package/src/cli/commands/search.ts +33 -0
  35. package/src/cli/commands/show.ts +42 -0
  36. package/src/cli/commands/stats.ts +23 -0
  37. package/src/cli/commands/today.ts +27 -0
  38. package/src/cli/commands/week.ts +93 -0
  39. package/src/cli/index.ts +294 -0
  40. package/src/cli/ui.ts +323 -0
  41. package/src/core/config.ts +69 -0
  42. package/src/core/discovery.ts +38 -0
  43. package/src/core/logger.ts +53 -0
  44. package/src/database/connection.ts +95 -0
  45. package/src/database/migration-data.ts +71 -0
  46. package/src/database/migrations/001_initial.sql +60 -0
  47. package/src/database/migrations/002_relational_tables.sql +22 -0
  48. package/src/database/migrations.ts +65 -0
  49. package/src/errors/index.ts +51 -0
  50. package/src/exporters/csv.ts +59 -0
  51. package/src/exporters/json.ts +8 -0
  52. package/src/exporters/markdown.ts +71 -0
  53. package/src/models/work-unit.ts +38 -0
  54. package/src/repositories/work-unit.ts +466 -0
  55. package/src/services/work-unit.ts +131 -0
  56. package/src/tests/cli/__snapshots__/cli-productivity.test.ts.snap +37 -0
  57. package/src/tests/cli/__snapshots__/cli.test.ts.snap +20 -0
  58. package/src/tests/cli/cli-productivity.test.ts +167 -0
  59. package/src/tests/cli/cli.test.ts +171 -0
  60. package/src/tests/core/config.test.ts +53 -0
  61. package/src/tests/core/discovery.test.ts +46 -0
  62. package/src/tests/database/migrations.test.ts +75 -0
  63. package/src/tests/repositories/work-unit.test.ts +243 -0
  64. package/src/tests/services/work-unit.test.ts +87 -0
  65. package/src/validators/work-unit.ts +63 -0
  66. package/tsconfig.json +16 -0
  67. package/tsup.config.ts +50 -0
  68. package/vitest.config.ts +8 -0
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # WorkLog
2
+
3
+ > **Engineering Context Manager** — *Git stores code. WorkLog stores engineering context.*
4
+
5
+ ---
6
+ ### Links:
7
+ - 📖 [Architecture Design Manual](ARCHITECTURE.md)
8
+ - 💾 [Advanced Installation Guide](INSTALLATION.md)
9
+ ---
10
+
11
+ WorkLog is a local-first, offline-first command-line tool designed to capture the **why**, the **blockers**, the **decisions**, and the **meaningful progress** of your daily engineering work. It helps you keep track of what you are doing so you never have to ask yourself, *"What was I doing yesterday?"*
12
+
13
+ ---
14
+
15
+ ## Technical Stack
16
+
17
+ - **Language:** TypeScript
18
+ - **Runtime:** Node.js (v20+ recommended)
19
+ - **CLI Framework:** Commander.js
20
+ - **Database:** SQLite (via `better-sqlite3` native drivers)
21
+ - **Validation:** Zod
22
+ - **Logging:** Pino (saved to `.worklog/worklog.log`)
23
+
24
+ ---
25
+
26
+ ## Installation
27
+
28
+ WorkLog is configured to install and run locally.
29
+
30
+ 1. Ensure you have **pnpm** and Node.js installed.
31
+ 2. Clone this repository.
32
+ 3. Install dependencies:
33
+ ```bash
34
+ pnpm install
35
+ ```
36
+ 4. Build the application:
37
+ ```bash
38
+ pnpm build
39
+ ```
40
+ 5. Link the command globally (optional):
41
+ ```bash
42
+ pnpm link --global
43
+ ```
44
+
45
+ Now you can invoke WorkLog with:
46
+ ```bash
47
+ worklog --help
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Core Philosophy
53
+
54
+ - **Local Scope:** Every project initializes its own database in a `.worklog/` directory (just like `.git` in Git).
55
+ - **Fast Interactive Prompts:** If you omit arguments, `worklog add` automatically prompts you through choices.
56
+ - **Machine Readable:** Add the `--json` flag to print structured data for IDE plugins or scripts.
57
+ - **Dedicated Exporters:** Supports Markdown, CSV, and JSON outputs for clean copy-pasting.
58
+
59
+ ---
60
+
61
+ ## CLI Usage & Reference
62
+
63
+ ### 1. Initialize a Project (`init` / `i`)
64
+ Initializes a new context tracker in the current directory.
65
+
66
+ ```bash
67
+ worklog init
68
+ # or
69
+ worklog i
70
+ ```
71
+
72
+ **Output:**
73
+ ```
74
+ Initialized empty WorkLog repository in /path/to/project/.worklog/
75
+ ```
76
+
77
+ ---
78
+
79
+ ### 2. Create a Work Unit (`add` / `a`)
80
+ Create an entry representing a Feature, Bug, Refactor, Spike, Research, or Decision.
81
+
82
+ **Non-Interactive Mode:**
83
+ ```bash
84
+ worklog add --title "Setup authentication endpoint" --type Feature --status "In Progress" --module Auth --tags backend,security
85
+ # or
86
+ worklog a -t "Fix connection leak" --type Bug --status Completed
87
+ ```
88
+
89
+ **Interactive Mode:**
90
+ Simply run `worklog add` (or `worklog a`) without flags in a TTY terminal, and it will guide you:
91
+ ```
92
+ ? Title: › Setup auth
93
+ ? Type: › Feature
94
+ ? Status: › Planned
95
+ ? Description (optional): › Initial JWT setup
96
+ ? Module (optional): › auth
97
+ ? Tags (comma-separated, optional): › backend,security
98
+ ? Next Step (optional): › write unit tests
99
+ ```
100
+
101
+ ---
102
+
103
+ ### 3. List Work Units (`list` / `ls`)
104
+ Lists recent entries.
105
+
106
+ ```bash
107
+ worklog list
108
+ # or
109
+ worklog ls
110
+ ```
111
+
112
+ **Output:**
113
+ ```
114
+ ID Title Type Status Module Priority Created At
115
+ #2 Fix connection leak Bug Completed - - Jul 26
116
+ #1 Setup authentication... Feature In Progress Auth - Jul 26
117
+ ```
118
+
119
+ **Options:**
120
+ - `-l, --limit <count>`: Limit output (default is 20).
121
+ - `-s, --status <status>`: Filter list by status.
122
+ - `-t, --type <type>`: Filter list by type.
123
+ - `--json`: Output machine-readable JSON array.
124
+
125
+ ---
126
+
127
+ ### 4. Show Details (`show` / `sh`)
128
+ Displays detailed card information for a single Work Unit.
129
+
130
+ ```bash
131
+ worklog show 1
132
+ # or
133
+ worklog sh 1
134
+ ```
135
+
136
+ ---
137
+
138
+ ### 5. Multi-criteria Search (`search` / `s`)
139
+ Perform case-insensitive text search querying the SQLite FTS5 virtual table, combined with category filters.
140
+
141
+ ```bash
142
+ # Search text
143
+ worklog search "connection leak"
144
+
145
+ # Search by tags and module
146
+ worklog search --tag backend --module auth
147
+
148
+ # Combined keyword and status query
149
+ worklog search "setup" --status "In Progress"
150
+ ```
151
+
152
+ **Options:**
153
+ - `--tag <tag>`: Filter results by tag.
154
+ - `--module <module>`: Filter results by module.
155
+ - `--status <status>`: Filter results by status.
156
+ - `--type <type>`: Filter results by type.
157
+ - `--json`: Return structured matches as JSON.
158
+
159
+ ---
160
+
161
+ ### 6. Today's Progress (`today` / `t`)
162
+ Display work units created or updated today since midnight.
163
+
164
+ ```bash
165
+ worklog today
166
+ # or
167
+ worklog t
168
+ ```
169
+
170
+ ---
171
+
172
+ ### 7. Weekly Summary (`week` / `w`)
173
+ Provides a weekly summary of progress grouped by engineering categories (features completed, bugs fixed, decisions made, blockers encountered, research performed) and prints status/type distribution counts.
174
+
175
+ ```bash
176
+ worklog week
177
+ # or
178
+ worklog w
179
+ ```
180
+
181
+ ---
182
+
183
+ ### 8. Project Dashboard (`dashboard` / `db`)
184
+ Displays a focused overview answering three core developer questions:
185
+ 1. *What am I currently working on?* (In Progress items)
186
+ 2. *What needs attention?* (Blocked or Planned items)
187
+ 3. *What changed recently?* (Recently updated items)
188
+
189
+ ```bash
190
+ worklog dashboard
191
+ # or
192
+ worklog db
193
+ ```
194
+
195
+ ---
196
+
197
+ ### 9. Resume Last Work (`continue` / `co`)
198
+ Resumes the most recently touched unfinished task (Planned, In Progress, Blocked), shifting its status to `In Progress` and updating its time marker.
199
+ If multiple unfinished units exist, it prompts you to select one (sorted by update date, most recent first).
200
+
201
+ ```bash
202
+ worklog continue
203
+ # or
204
+ worklog co
205
+ ```
206
+
207
+ ---
208
+
209
+ ### 10. Analytical Insights (`stats` / `st`)
210
+ Shows high-level metrics (totals, category distributions, and daily activity logs over the past 7 days) in clean text bars.
211
+
212
+ ```bash
213
+ worklog stats
214
+ # or
215
+ worklog st
216
+ ```
217
+
218
+ ---
219
+
220
+ ### 11. Export Context (`export` / `ex`)
221
+ Exports log entries to Markdown, CSV, or JSON. Supports writing directly to files or stdout piping.
222
+
223
+ ```bash
224
+ # Export everything to markdown file
225
+ worklog export --format markdown --output docs/summary.md
226
+
227
+ # Pipe CSV data
228
+ worklog export --format csv > data_export.csv
229
+
230
+ # Export a single work unit
231
+ worklog export --id 1 --format markdown
232
+ ```
233
+
234
+ **Options:**
235
+ - `-f, --format <format>`: Output format (`markdown` / `md`, `csv`, `json`). Defaults to `markdown`.
236
+ - `-o, --output <file>`: Writes data to a physical file.
237
+ - `--id <id>`: Exports only the unit matching this ID.
238
+
239
+ ---
240
+
241
+ ## Error Codes
242
+
243
+ The CLI will exit with:
244
+ - `0` on successful operation execution.
245
+ - `1` on user/validation errors (e.g. invalid arguments, missing fields, not in a repository).
246
+ - `2` on system/database failures (e.g. SQLite corruption, unhandled crashes).
package/SECURITY.md ADDED
@@ -0,0 +1,18 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Only the latest release of WorkLog is actively supported for security updates.
6
+
7
+ | Version | Supported |
8
+ | ------- | ------------------ |
9
+ | v0.1.x | :white_check_mark: |
10
+
11
+ ## Reporting a Vulnerability
12
+
13
+ If you discover a security vulnerability in this project, please **do not** open a public issue. Instead, report it privately to the maintainers.
14
+
15
+ Please send security disclosures and details of the vulnerability to:
16
+ - **Email:** security@worklog.local (placeholder, replace with actual security contact)
17
+
18
+ We will endeavor to respond to your report within 48 hours and outline a plan for remediation.
@@ -0,0 +1,149 @@
1
+ import { describe, it } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import Database from 'better-sqlite3';
5
+ import { runMigrations } from '../src/database/migrations.js';
6
+ import { WorkUnitRepository } from '../src/repositories/work-unit.js';
7
+ import { WorkUnitService } from '../src/services/work-unit.js';
8
+ import { exportToMarkdown } from '../src/exporters/markdown.js';
9
+ import { exportToCSV } from '../src/exporters/csv.js';
10
+ import { exportToJSON } from '../src/exporters/json.js';
11
+
12
+ describe('WorkLog Performance Benchmark', () => {
13
+ const benchDir = path.join(process.cwd(), 'benchmarks', 'temp-bench-db');
14
+
15
+ it('should run performance metrics with 10,000 work units', () => {
16
+ if (fs.existsSync(benchDir)) {
17
+ fs.rmSync(benchDir, { recursive: true, force: true });
18
+ }
19
+ fs.mkdirSync(benchDir, { recursive: true });
20
+
21
+ console.log('\n--- STARTING WORKLOG BENCHMARK ---');
22
+
23
+ // 1. Connection & Migration Benchmark
24
+ const startConn = performance.now();
25
+ const db = new Database(path.join(benchDir, 'bench.db'));
26
+ runMigrations(db);
27
+ const endConn = performance.now();
28
+ const connTime = endConn - startConn;
29
+ console.log(`Database Connection & Migrations: ${connTime.toFixed(2)}ms`);
30
+
31
+ const repo = new WorkUnitRepository(db);
32
+ const service = new WorkUnitService(repo);
33
+
34
+ // 2. Seeding 10,000 work units
35
+ console.log('Seeding 10,000 work units...');
36
+ const startSeed = performance.now();
37
+
38
+ // Seed using a transaction to run extremely fast
39
+ const seedTx = db.transaction(() => {
40
+ const types = [
41
+ 'Feature',
42
+ 'Bug',
43
+ 'Spike',
44
+ 'Refactor',
45
+ 'Improvement',
46
+ 'Research',
47
+ 'Decision',
48
+ 'Blocker',
49
+ 'Review',
50
+ 'Idea',
51
+ ];
52
+ const statuses = ['Planned', 'In Progress', 'Blocked', 'Completed', 'Cancelled'];
53
+ const priorities = ['Low', 'Medium', 'High', null];
54
+ const modules = ['auth', 'billing', 'gateway', 'core', 'ui', null];
55
+
56
+ const insertStmt = db.prepare(`
57
+ INSERT INTO work_units (
58
+ title, type, status, priority, module, description, next_step, decision, blocker, notes, created_at, updated_at
59
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
60
+ `);
61
+
62
+ const insertTag = db.prepare('INSERT INTO work_unit_tags (work_unit_id, tag) VALUES (?, ?)');
63
+
64
+ for (let i = 1; i <= 10000; i++) {
65
+ const type = types[i % types.length];
66
+ const status = statuses[i % statuses.length];
67
+ const priority = priorities[i % priorities.length];
68
+ const mod = modules[i % modules.length];
69
+
70
+ const now = new Date().toISOString();
71
+
72
+ insertStmt.run(
73
+ `Work Unit Title Number ${i} is long and descriptive`,
74
+ type,
75
+ status,
76
+ priority,
77
+ mod,
78
+ `Detailed description of work unit ${i} containing some sample logs.`,
79
+ i % 3 === 0 ? 'Review next milestone tasks' : null,
80
+ type === 'Decision' ? `Decision approved for item ${i}` : null,
81
+ status === 'Blocked' ? `Blocked by gateway API down for item ${i}` : null,
82
+ `Some private developer notes for unit ${i}`,
83
+ now,
84
+ now,
85
+ );
86
+
87
+ if (i % 2 === 0) {
88
+ insertTag.run(i, 'backend');
89
+ insertTag.run(i, `tag-${i % 20}`);
90
+ }
91
+ }
92
+ });
93
+
94
+ seedTx();
95
+ const endSeed = performance.now();
96
+ console.log(`Seeding 10,000 units: ${(endSeed - startSeed).toFixed(2)}ms`);
97
+
98
+ // 3. Stats Query Speed
99
+ const startStats = performance.now();
100
+ service.getStats(new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString());
101
+ const endStats = performance.now();
102
+ console.log(`Stats Aggregation (10k items): ${(endStats - startStats).toFixed(2)}ms`);
103
+
104
+ // 4. Search Filter Speed
105
+ const startSearch = performance.now();
106
+ const searchResult = service.searchWorkUnits('descriptive', {
107
+ status: 'In Progress',
108
+ tag: 'backend',
109
+ });
110
+ const endSearch = performance.now();
111
+ console.log(
112
+ `Case-insensitive Multi-criteria Search (10k items, matching ${searchResult.length} units): ${(endSearch - startSearch).toFixed(2)}ms`,
113
+ );
114
+
115
+ // 5. Dashboard Generation Speed
116
+ const startDash = performance.now();
117
+ const inProgress = service.getWorkUnitsByStatus('In Progress');
118
+ const recentlyUpdated = service.getRecentWorkUnits(5);
119
+ const blocked = service.getWorkUnitsByStatus('Blocked');
120
+ const planned = service.getWorkUnitsByStatus('Planned');
121
+ const needsAttention = [...blocked, ...planned].slice(0, 5);
122
+ const endDash = performance.now();
123
+ console.log(
124
+ `Dashboard Query Speed (10k items, loading ${inProgress.length + recentlyUpdated.length + needsAttention.length} visual links): ${(endDash - startDash).toFixed(2)}ms`,
125
+ );
126
+
127
+ // 6. Exports Formatting Speed
128
+ const allUnits = service.getRecentWorkUnits(10000);
129
+
130
+ const startMd = performance.now();
131
+ exportToMarkdown(allUnits);
132
+ const endMd = performance.now();
133
+ console.log(`Markdown Export format (10k items): ${(endMd - startMd).toFixed(2)}ms`);
134
+
135
+ const startCSV = performance.now();
136
+ exportToCSV(allUnits);
137
+ const endCSV = performance.now();
138
+ console.log(`CSV Export format (10k items): ${(endCSV - startCSV).toFixed(2)}ms`);
139
+
140
+ const startJSON = performance.now();
141
+ exportToJSON(allUnits);
142
+ const endJSON = performance.now();
143
+ console.log(`JSON Export format (10k items): ${(endJSON - startJSON).toFixed(2)}ms`);
144
+
145
+ db.close();
146
+ fs.rmSync(benchDir, { recursive: true, force: true });
147
+ console.log('--- BENCHMARK COMPLETE ---\n');
148
+ });
149
+ });
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['benchmarks/benchmark.ts'],
6
+ globals: true,
7
+ environment: 'node',
8
+ },
9
+ });
@@ -0,0 +1,60 @@
1
+ -- Table to track database migrations
2
+ CREATE TABLE IF NOT EXISTS schema_migrations (
3
+ version TEXT PRIMARY KEY,
4
+ applied_at TEXT NOT NULL
5
+ );
6
+
7
+ -- Core work_units table
8
+ CREATE TABLE IF NOT EXISTS work_units (
9
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
10
+ title TEXT NOT NULL,
11
+ type TEXT NOT NULL,
12
+ status TEXT NOT NULL,
13
+ priority TEXT,
14
+ module TEXT,
15
+ description TEXT,
16
+ next_step TEXT,
17
+ decision TEXT,
18
+ blocker TEXT,
19
+ tags TEXT, -- JSON array of strings
20
+ related_work_units TEXT, -- JSON array of integers
21
+ notes TEXT,
22
+ created_at TEXT NOT NULL,
23
+ updated_at TEXT NOT NULL
24
+ );
25
+
26
+ -- Performance indexes for status, type, and dates
27
+ CREATE INDEX IF NOT EXISTS idx_work_units_status ON work_units(status);
28
+ CREATE INDEX IF NOT EXISTS idx_work_units_type ON work_units(type);
29
+ CREATE INDEX IF NOT EXISTS idx_work_units_created_at ON work_units(created_at);
30
+
31
+ -- SQLite FTS5 Search Index Table using work_units as external content
32
+ CREATE VIRTUAL TABLE IF NOT EXISTS work_units_fts USING fts5(
33
+ title,
34
+ description,
35
+ decision,
36
+ blocker,
37
+ notes,
38
+ content='work_units',
39
+ content_rowid='id'
40
+ );
41
+
42
+ -- Trigger to sync insert events to FTS
43
+ CREATE TRIGGER IF NOT EXISTS work_units_ai AFTER INSERT ON work_units BEGIN
44
+ INSERT INTO work_units_fts(rowid, title, description, decision, blocker, notes)
45
+ VALUES (new.id, new.title, new.description, new.decision, new.blocker, new.notes);
46
+ END;
47
+
48
+ -- Trigger to sync delete events to FTS
49
+ CREATE TRIGGER IF NOT EXISTS work_units_ad AFTER DELETE ON work_units BEGIN
50
+ INSERT INTO work_units_fts(work_units_fts, rowid, title, description, decision, blocker, notes)
51
+ VALUES('delete', old.id, old.title, old.description, old.decision, old.blocker, old.notes);
52
+ END;
53
+
54
+ -- Trigger to sync update events to FTS
55
+ CREATE TRIGGER IF NOT EXISTS work_units_au AFTER UPDATE ON work_units BEGIN
56
+ INSERT INTO work_units_fts(work_units_fts, rowid, title, description, decision, blocker, notes)
57
+ VALUES('delete', old.id, old.title, old.description, old.decision, old.blocker, old.notes);
58
+ INSERT INTO work_units_fts(rowid, title, description, decision, blocker, notes)
59
+ VALUES (new.id, new.title, new.description, new.decision, new.blocker, new.notes);
60
+ END;
@@ -0,0 +1,22 @@
1
+ -- Create work_unit_tags table for indexing and querying tags
2
+ CREATE TABLE IF NOT EXISTS work_unit_tags (
3
+ work_unit_id INTEGER NOT NULL,
4
+ tag TEXT NOT NULL,
5
+ PRIMARY KEY (work_unit_id, tag),
6
+ FOREIGN KEY (work_unit_id) REFERENCES work_units(id) ON DELETE CASCADE
7
+ );
8
+
9
+ CREATE INDEX IF NOT EXISTS idx_work_unit_tags_tag ON work_unit_tags(tag);
10
+
11
+ -- Create work_unit_relations table for modeling links between work units
12
+ CREATE TABLE IF NOT EXISTS work_unit_relations (
13
+ work_unit_id INTEGER NOT NULL,
14
+ related_work_unit_id INTEGER NOT NULL,
15
+ PRIMARY KEY (work_unit_id, related_work_unit_id),
16
+ FOREIGN KEY (work_unit_id) REFERENCES work_units(id) ON DELETE CASCADE,
17
+ FOREIGN KEY (related_work_unit_id) REFERENCES work_units(id) ON DELETE CASCADE
18
+ );
19
+
20
+ -- Drop unused JSON columns from the main work_units table
21
+ ALTER TABLE work_units DROP COLUMN tags;
22
+ ALTER TABLE work_units DROP COLUMN related_work_units;