@groeponline/pi-missions 0.2.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.
@@ -0,0 +1,713 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/database/index.ts
4
+ import { createRequire } from "module";
5
+ import { readFileSync } from "fs";
6
+ import { dirname, join } from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { existsSync, mkdirSync } from "fs";
9
+ import { homedir } from "os";
10
+ var db = null;
11
+ var require2 = createRequire(import.meta.url);
12
+ var databaseDriver = null;
13
+ var CURRENT_SCHEMA_VERSION = 1;
14
+ function describeLoadError(error) {
15
+ return error instanceof Error ? error.message : String(error);
16
+ }
17
+ function openSqliteDatabase(filename) {
18
+ try {
19
+ const BetterSqlite3 = require2("better-sqlite3");
20
+ databaseDriver = "better-sqlite3";
21
+ return new BetterSqlite3(filename);
22
+ } catch (betterSqliteError) {
23
+ try {
24
+ const { DatabaseSync } = require2("node:sqlite");
25
+ databaseDriver = "node:sqlite";
26
+ return new DatabaseSync(filename);
27
+ } catch (nodeSqliteError) {
28
+ throw new Error(
29
+ `Unable to initialize SQLite. Run on Node.js >=22.5.0, or install better-sqlite3 manually. better-sqlite3: ${describeLoadError(betterSqliteError)}; node:sqlite: ${describeLoadError(nodeSqliteError)}`
30
+ );
31
+ }
32
+ }
33
+ }
34
+ function applyPragma(database, pragma) {
35
+ const maybePragma = database.pragma;
36
+ if (typeof maybePragma === "function") {
37
+ maybePragma.call(database, pragma);
38
+ } else {
39
+ database.exec(`PRAGMA ${pragma}`);
40
+ }
41
+ }
42
+ function resolveSchemaPath() {
43
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
44
+ const candidates = [
45
+ join(moduleDir, "schema.sql"),
46
+ join(moduleDir, "database", "schema.sql"),
47
+ join(moduleDir, "..", "database", "schema.sql"),
48
+ join(process.cwd(), "src", "database", "schema.sql"),
49
+ join(process.cwd(), "dist", "database", "schema.sql")
50
+ ];
51
+ const schemaPath = candidates.find(existsSync);
52
+ if (!schemaPath) {
53
+ throw new Error(`Unable to locate database schema.sql. Searched: ${candidates.join(", ")}`);
54
+ }
55
+ return schemaPath;
56
+ }
57
+ function getDatabase() {
58
+ if (!db) {
59
+ const dbPath = getDatabasePath();
60
+ let dbDir;
61
+ let dbFile;
62
+ if (dbPath === ":memory:") {
63
+ dbDir = "";
64
+ dbFile = dbPath;
65
+ } else if (dbPath.endsWith(".db")) {
66
+ dbDir = dirname(dbPath);
67
+ dbFile = dbPath;
68
+ } else {
69
+ dbDir = dbPath;
70
+ dbFile = join(dbPath, "pi-missions.db");
71
+ }
72
+ if (dbDir && !existsSync(dbDir)) {
73
+ mkdirSync(dbDir, { recursive: true });
74
+ }
75
+ const candidateDb = openSqliteDatabase(dbFile);
76
+ try {
77
+ applyPragma(candidateDb, "journal_mode = WAL");
78
+ applyPragma(candidateDb, "foreign_keys = ON");
79
+ initializeSchema(candidateDb);
80
+ runMigrations(candidateDb);
81
+ db = candidateDb;
82
+ } catch (error) {
83
+ candidateDb.close();
84
+ throw error;
85
+ }
86
+ }
87
+ return db;
88
+ }
89
+ function getDatabasePath() {
90
+ return process.env.PI_MISSIONS_DB_PATH || join(homedir(), ".pi", "missions", "database");
91
+ }
92
+ function initializeSchema(db2) {
93
+ const schemaPath = resolveSchemaPath();
94
+ const schema = readFileSync(schemaPath, "utf-8");
95
+ try {
96
+ db2.exec(schema);
97
+ } catch (error) {
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ throw new Error(`Failed to initialize database schema from ${schemaPath}: ${message}`);
100
+ }
101
+ }
102
+ function ensureSchemaVersionTable(database) {
103
+ database.exec(`
104
+ CREATE TABLE IF NOT EXISTS schema_version (
105
+ id INTEGER PRIMARY KEY CHECK(id = 1),
106
+ version INTEGER NOT NULL,
107
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
108
+ );
109
+ `);
110
+ }
111
+ function getSchemaVersion(database) {
112
+ ensureSchemaVersionTable(database);
113
+ const row = database.prepare("SELECT version FROM schema_version WHERE id = 1").get();
114
+ return row?.version ?? 0;
115
+ }
116
+ function setSchemaVersion(database, version) {
117
+ database.prepare(`
118
+ INSERT INTO schema_version (id, version, updated_at)
119
+ VALUES (1, ?, ?)
120
+ ON CONFLICT(id) DO UPDATE SET
121
+ version = excluded.version,
122
+ updated_at = excluded.updated_at
123
+ `).run(version, Date.now());
124
+ }
125
+ function baselineExistingSchema(database) {
126
+ const row = database.prepare(`
127
+ SELECT 1 AS exists_flag
128
+ FROM sqlite_master
129
+ WHERE type = 'table' AND name = 'missions'
130
+ LIMIT 1
131
+ `).get();
132
+ if (row?.exists_flag === 1) {
133
+ setSchemaVersion(database, 1);
134
+ }
135
+ }
136
+ function runMigrations(database) {
137
+ ensureSchemaVersionTable(database);
138
+ const migrateBody = () => {
139
+ let version = getSchemaVersion(database);
140
+ if (version === 0) {
141
+ baselineExistingSchema(database);
142
+ version = getSchemaVersion(database);
143
+ }
144
+ if (version === 0) {
145
+ setSchemaVersion(database, CURRENT_SCHEMA_VERSION);
146
+ }
147
+ };
148
+ const maybeTransaction = database.transaction;
149
+ if (typeof maybeTransaction === "function") {
150
+ const migrate = maybeTransaction.call(database, migrateBody);
151
+ migrate();
152
+ return;
153
+ }
154
+ database.exec("BEGIN");
155
+ try {
156
+ migrateBody();
157
+ database.exec("COMMIT");
158
+ } catch (error) {
159
+ database.exec("ROLLBACK");
160
+ throw error;
161
+ }
162
+ }
163
+ var MISSION_UPDATABLE_COLUMNS = /* @__PURE__ */ new Set([
164
+ "title",
165
+ "goal",
166
+ "status",
167
+ "updated_at",
168
+ "completed_at",
169
+ "total_tokens",
170
+ "total_features",
171
+ "features_completed",
172
+ "features_failed",
173
+ "success_rate",
174
+ "tags",
175
+ "metadata"
176
+ ]);
177
+ var MissionRepository = class {
178
+ db;
179
+ constructor(db2) {
180
+ this.db = db2;
181
+ }
182
+ create(mission) {
183
+ const now = Date.now();
184
+ const stmt = this.db.prepare(`
185
+ INSERT INTO missions (id, title, goal, status, created_at, updated_at, completed_at,
186
+ total_tokens, total_features, features_completed, features_failed,
187
+ success_rate, tags, metadata)
188
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
189
+ `);
190
+ stmt.run(
191
+ mission.id,
192
+ mission.title,
193
+ mission.goal,
194
+ mission.status,
195
+ now,
196
+ now,
197
+ mission.completed_at,
198
+ mission.total_tokens,
199
+ mission.total_features,
200
+ mission.features_completed,
201
+ mission.features_failed,
202
+ mission.success_rate,
203
+ mission.tags,
204
+ mission.metadata
205
+ );
206
+ return this.findById(mission.id);
207
+ }
208
+ findById(id) {
209
+ return this.db.prepare("SELECT * FROM missions WHERE id = ?").get(id);
210
+ }
211
+ findAll(limit = 100, offset = 0) {
212
+ return this.db.prepare("SELECT * FROM missions ORDER BY updated_at DESC LIMIT ? OFFSET ?").all(limit, offset);
213
+ }
214
+ findByStatus(status) {
215
+ return this.db.prepare("SELECT * FROM missions WHERE status = ? ORDER BY updated_at DESC").all(status);
216
+ }
217
+ update(id, updates) {
218
+ const fields = Object.keys(updates).filter((k) => MISSION_UPDATABLE_COLUMNS.has(k));
219
+ if (fields.length === 0) return this.findById(id);
220
+ const setClause = fields.map((f) => `${f} = ?`).join(", ");
221
+ const values = fields.map((f) => updates[f]);
222
+ this.db.prepare(`UPDATE missions SET ${setClause}, updated_at = ? WHERE id = ?`).run(...values, Date.now(), id);
223
+ return this.findById(id);
224
+ }
225
+ delete(id) {
226
+ const result = this.db.prepare("DELETE FROM missions WHERE id = ?").run(id);
227
+ return result.changes > 0;
228
+ }
229
+ count() {
230
+ const row = this.db.prepare("SELECT COUNT(*) as count FROM missions").get();
231
+ return row?.count ?? 0;
232
+ }
233
+ };
234
+ var FEATURE_UPDATABLE_COLUMNS = /* @__PURE__ */ new Set([
235
+ "milestone_id",
236
+ "title",
237
+ "description",
238
+ "priority",
239
+ "status",
240
+ "depends_on",
241
+ "acceptance_criteria",
242
+ "sessions",
243
+ "tool_call_count",
244
+ "tokens_used",
245
+ "error_count",
246
+ "blockers",
247
+ "notes",
248
+ "started_at",
249
+ "completed_at",
250
+ "evidence"
251
+ ]);
252
+ var FeatureRepository = class {
253
+ db;
254
+ constructor(db2) {
255
+ this.db = db2;
256
+ }
257
+ create(feature) {
258
+ const stmt = this.db.prepare(`
259
+ INSERT INTO features (id, milestone_id, mission_id, title, description, priority, status,
260
+ depends_on, acceptance_criteria, sessions, tool_call_count, tokens_used,
261
+ error_count, blockers, notes, created_at, started_at, completed_at, evidence)
262
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
263
+ `);
264
+ stmt.run(
265
+ feature.id,
266
+ feature.milestone_id,
267
+ feature.mission_id,
268
+ feature.title,
269
+ feature.description,
270
+ feature.priority,
271
+ feature.status,
272
+ feature.depends_on,
273
+ feature.acceptance_criteria,
274
+ feature.sessions,
275
+ feature.tool_call_count,
276
+ feature.tokens_used,
277
+ feature.error_count,
278
+ feature.blockers,
279
+ feature.notes,
280
+ Date.now(),
281
+ feature.started_at,
282
+ feature.completed_at,
283
+ feature.evidence
284
+ );
285
+ return this.findById(feature.id, feature.mission_id);
286
+ }
287
+ findById(id, missionId) {
288
+ return this.db.prepare("SELECT * FROM features WHERE id = ? AND mission_id = ?").get(id, missionId);
289
+ }
290
+ findByMission(missionId) {
291
+ return this.db.prepare("SELECT * FROM features WHERE mission_id = ? ORDER BY created_at").all(missionId);
292
+ }
293
+ findByStatus(missionId, status) {
294
+ return this.db.prepare("SELECT * FROM features WHERE mission_id = ? AND status = ?").all(missionId, status);
295
+ }
296
+ update(id, missionId, updates) {
297
+ const fields = Object.keys(updates).filter((k) => FEATURE_UPDATABLE_COLUMNS.has(k));
298
+ if (fields.length === 0) return this.findById(id, missionId);
299
+ const setClause = fields.map((f) => `${f} = ?`).join(", ");
300
+ const values = fields.map((f) => updates[f]);
301
+ this.db.prepare(`UPDATE features SET ${setClause} WHERE id = ? AND mission_id = ?`).run(...values, id, missionId);
302
+ return this.findById(id, missionId);
303
+ }
304
+ delete(id, missionId) {
305
+ const result = this.db.prepare("DELETE FROM features WHERE id = ? AND mission_id = ?").run(id, missionId);
306
+ return result.changes > 0;
307
+ }
308
+ };
309
+ var HistoryRepository = class {
310
+ db;
311
+ constructor(db2) {
312
+ this.db = db2;
313
+ }
314
+ append(entry) {
315
+ const stmt = this.db.prepare(`
316
+ INSERT INTO history (mission_id, feature_id, event, note, details, timestamp, session_id)
317
+ VALUES (?, ?, ?, ?, ?, ?, ?)
318
+ `);
319
+ const result = stmt.run(
320
+ entry.mission_id,
321
+ entry.feature_id,
322
+ entry.event,
323
+ entry.note,
324
+ entry.details,
325
+ Date.now(),
326
+ entry.session_id
327
+ );
328
+ return this.db.prepare("SELECT * FROM history WHERE id = ?").get(result.lastInsertRowid);
329
+ }
330
+ findByMission(missionId, limit = 100) {
331
+ return this.db.prepare("SELECT * FROM history WHERE mission_id = ? ORDER BY timestamp DESC LIMIT ?").all(missionId, limit);
332
+ }
333
+ findByFeature(featureId, limit = 50) {
334
+ return this.db.prepare("SELECT * FROM history WHERE feature_id = ? ORDER BY timestamp DESC LIMIT ?").all(featureId, limit);
335
+ }
336
+ findByEvent(event, limit = 50) {
337
+ return this.db.prepare("SELECT * FROM history WHERE event = ? ORDER BY timestamp DESC LIMIT ?").all(event, limit);
338
+ }
339
+ search(query, limit = 50) {
340
+ return this.db.prepare(`
341
+ SELECT * FROM history
342
+ WHERE note LIKE ? OR event LIKE ?
343
+ ORDER BY timestamp DESC LIMIT ?
344
+ `).all(`%${query}%`, `%${query}%`, limit);
345
+ }
346
+ };
347
+ var LearningRepository = class {
348
+ db;
349
+ constructor(db2) {
350
+ this.db = db2;
351
+ }
352
+ create(learning) {
353
+ const stmt = this.db.prepare(`
354
+ INSERT INTO learnings (mission_id, feature_id, type, category, insight, confidence, applicable_to, context)
355
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
356
+ `);
357
+ const result = stmt.run(
358
+ learning.mission_id,
359
+ learning.feature_id,
360
+ learning.type,
361
+ learning.category,
362
+ learning.insight,
363
+ learning.confidence,
364
+ learning.applicable_to,
365
+ learning.context
366
+ );
367
+ return this.db.prepare("SELECT * FROM learnings WHERE id = ?").get(result.lastInsertRowid);
368
+ }
369
+ findByType(type, limit = 50) {
370
+ return this.db.prepare("SELECT * FROM learnings WHERE type = ? ORDER BY confidence DESC LIMIT ?").all(type, limit);
371
+ }
372
+ findByCategory(category, limit = 50) {
373
+ return this.db.prepare("SELECT * FROM learnings WHERE category = ? ORDER BY confidence DESC LIMIT ?").all(category, limit);
374
+ }
375
+ findRelevant(tags, limit = 10) {
376
+ if (tags.length === 0) return [];
377
+ const placeholders = tags.map(() => "?").join(",");
378
+ return this.db.prepare(`
379
+ SELECT * FROM learnings
380
+ WHERE json_valid(learnings.applicable_to)
381
+ AND EXISTS (
382
+ SELECT 1
383
+ FROM json_each(learnings.applicable_to)
384
+ WHERE json_each.value IN (${placeholders})
385
+ )
386
+ ORDER BY confidence DESC, used_count DESC
387
+ LIMIT ?
388
+ `).all(...tags, limit);
389
+ }
390
+ recordUsage(id, success) {
391
+ if (success) {
392
+ this.db.prepare("UPDATE learnings SET used_count = used_count + 1, success_count = success_count + 1 WHERE id = ?").run(id);
393
+ } else {
394
+ this.db.prepare("UPDATE learnings SET used_count = used_count + 1 WHERE id = ?").run(id);
395
+ }
396
+ }
397
+ };
398
+ var PatternRepository = class {
399
+ db;
400
+ constructor(db2) {
401
+ this.db = db2;
402
+ }
403
+ create(pattern) {
404
+ const stmt = this.db.prepare(`
405
+ INSERT INTO patterns (pattern_type, name, description, pattern_data, success_count, failure_count,
406
+ success_rate, avg_duration_ms, avg_tokens, example_missions, tags)
407
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
408
+ `);
409
+ const result = stmt.run(
410
+ pattern.pattern_type,
411
+ pattern.name,
412
+ pattern.description,
413
+ pattern.pattern_data,
414
+ pattern.success_count,
415
+ pattern.failure_count,
416
+ pattern.success_rate,
417
+ pattern.avg_duration_ms,
418
+ pattern.avg_tokens,
419
+ pattern.example_missions,
420
+ pattern.tags
421
+ );
422
+ return this.db.prepare("SELECT * FROM patterns WHERE id = ?").get(result.lastInsertRowid);
423
+ }
424
+ findByType(type, limit = 20) {
425
+ return this.db.prepare("SELECT * FROM patterns WHERE pattern_type = ? ORDER BY success_rate DESC LIMIT ?").all(type, limit);
426
+ }
427
+ findSuccessful(limit = 20) {
428
+ return this.db.prepare("SELECT * FROM patterns WHERE success_rate > 0.7 ORDER BY success_rate DESC LIMIT ?").all(limit);
429
+ }
430
+ recordOutcome(id, success) {
431
+ if (success) {
432
+ this.db.prepare(`
433
+ UPDATE patterns
434
+ SET success_count = success_count + 1,
435
+ success_rate = CAST(success_count + 1 AS REAL) / (success_count + failure_count + 1),
436
+ updated_at = ?
437
+ WHERE id = ?
438
+ `).run(Date.now(), id);
439
+ } else {
440
+ this.db.prepare(`
441
+ UPDATE patterns
442
+ SET failure_count = failure_count + 1,
443
+ success_rate = CAST(success_count AS REAL) / (success_count + failure_count + 1),
444
+ updated_at = ?
445
+ WHERE id = ?
446
+ `).run(Date.now(), id);
447
+ }
448
+ }
449
+ };
450
+ var TemplateRepository = class {
451
+ db;
452
+ constructor(db2) {
453
+ this.db = db2;
454
+ }
455
+ findById(id) {
456
+ return this.db.prepare("SELECT * FROM templates WHERE id = ?").get(id);
457
+ }
458
+ findAll(limit = 50) {
459
+ return this.db.prepare("SELECT * FROM templates ORDER BY rating DESC, usage_count DESC LIMIT ?").all(limit);
460
+ }
461
+ findByTags(tags) {
462
+ if (tags.length === 0) return [];
463
+ const placeholders = tags.map(() => "?").join(",");
464
+ return this.db.prepare(`
465
+ SELECT * FROM templates
466
+ WHERE json_valid(templates.tags)
467
+ AND EXISTS (
468
+ SELECT 1
469
+ FROM json_each(templates.tags)
470
+ WHERE json_each.value IN (${placeholders})
471
+ )
472
+ ORDER BY rating DESC
473
+ `).all(...tags);
474
+ }
475
+ create(template) {
476
+ const now = Date.now();
477
+ const stmt = this.db.prepare(`
478
+ INSERT INTO templates (id, name, description, author, version, content, tags, difficulty,
479
+ estimated_time_hours, estimated_tokens, usage_count, rating, rating_count,
480
+ created_at, updated_at, is_builtin)
481
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, ?, ?, ?)
482
+ `);
483
+ stmt.run(
484
+ template.id,
485
+ template.name,
486
+ template.description,
487
+ template.author,
488
+ template.version,
489
+ template.content,
490
+ template.tags,
491
+ template.difficulty,
492
+ template.estimated_time_hours,
493
+ template.estimated_tokens,
494
+ now,
495
+ now,
496
+ template.is_builtin
497
+ );
498
+ return this.findById(template.id);
499
+ }
500
+ incrementUsage(id) {
501
+ this.db.prepare("UPDATE templates SET usage_count = usage_count + 1, updated_at = ? WHERE id = ?").run(Date.now(), id);
502
+ }
503
+ rate(id, rating) {
504
+ this.db.prepare(`
505
+ UPDATE templates
506
+ SET rating = (rating * rating_count + ?) / (rating_count + 1),
507
+ rating_count = rating_count + 1,
508
+ updated_at = ?
509
+ WHERE id = ?
510
+ `).run(rating, Date.now(), id);
511
+ }
512
+ };
513
+ var MetricRepository = class {
514
+ db;
515
+ constructor(db2) {
516
+ this.db = db2;
517
+ }
518
+ record(metric) {
519
+ const stmt = this.db.prepare(`
520
+ INSERT INTO metrics (metric_type, metric_name, value, unit, tags, recorded_at, period_start, period_end)
521
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
522
+ `);
523
+ const result = stmt.run(
524
+ metric.metric_type,
525
+ metric.metric_name,
526
+ metric.value,
527
+ metric.unit,
528
+ metric.tags,
529
+ Date.now(),
530
+ metric.period_start,
531
+ metric.period_end
532
+ );
533
+ return this.db.prepare("SELECT * FROM metrics WHERE id = ?").get(result.lastInsertRowid);
534
+ }
535
+ findByType(type, name, limit = 100) {
536
+ if (name) {
537
+ return this.db.prepare("SELECT * FROM metrics WHERE metric_type = ? AND metric_name = ? ORDER BY recorded_at DESC LIMIT ?").all(type, name, limit);
538
+ }
539
+ return this.db.prepare("SELECT * FROM metrics WHERE metric_type = ? ORDER BY recorded_at DESC LIMIT ?").all(type, limit);
540
+ }
541
+ getAggregated(type, name, periodMs) {
542
+ const since = Date.now() - periodMs;
543
+ const row = this.db.prepare(`
544
+ SELECT
545
+ AVG(value) as avg,
546
+ MIN(value) as min,
547
+ MAX(value) as max,
548
+ COUNT(*) as count
549
+ FROM metrics
550
+ WHERE metric_type = ? AND metric_name = ? AND recorded_at > ?
551
+ `).get(type, name, since);
552
+ return {
553
+ avg: row?.avg ?? 0,
554
+ min: row?.min ?? 0,
555
+ max: row?.max ?? 0,
556
+ count: row?.count ?? 0
557
+ };
558
+ }
559
+ };
560
+ var PredictionRepository = class {
561
+ db;
562
+ constructor(db2) {
563
+ this.db = db2;
564
+ }
565
+ create(prediction) {
566
+ const stmt = this.db.prepare(`
567
+ INSERT INTO predictions (mission_id, feature_id, prediction_type, predicted_value, actual_value, confidence, model_version)
568
+ VALUES (?, ?, ?, ?, ?, ?, ?)
569
+ `);
570
+ const result = stmt.run(
571
+ prediction.mission_id,
572
+ prediction.feature_id,
573
+ prediction.prediction_type,
574
+ prediction.predicted_value,
575
+ prediction.actual_value ?? null,
576
+ prediction.confidence,
577
+ prediction.model_version
578
+ );
579
+ return this.db.prepare("SELECT * FROM predictions WHERE id = ?").get(result.lastInsertRowid);
580
+ }
581
+ validate(id, actualValue) {
582
+ const prediction = this.db.prepare("SELECT * FROM predictions WHERE id = ?").get(id);
583
+ if (!prediction) return;
584
+ const accuracy = 1 - Math.abs(prediction.predicted_value - actualValue) / Math.max(prediction.predicted_value, actualValue);
585
+ this.db.prepare(`
586
+ UPDATE predictions
587
+ SET actual_value = ?, accuracy = ?, validated_at = ?
588
+ WHERE id = ?
589
+ `).run(actualValue, accuracy, Date.now(), id);
590
+ }
591
+ findByType(type, limit = 50) {
592
+ return this.db.prepare("SELECT * FROM predictions WHERE prediction_type = ? ORDER BY created_at DESC LIMIT ?").all(type, limit);
593
+ }
594
+ getAccuracy(type) {
595
+ const row = this.db.prepare(`
596
+ SELECT AVG(accuracy) as avg_accuracy
597
+ FROM predictions
598
+ WHERE prediction_type = ? AND accuracy IS NOT NULL
599
+ `).get(type);
600
+ return row?.avg_accuracy ?? 0;
601
+ }
602
+ };
603
+ function getRepositories() {
604
+ const db2 = getDatabase();
605
+ return {
606
+ missions: new MissionRepository(db2),
607
+ features: new FeatureRepository(db2),
608
+ history: new HistoryRepository(db2),
609
+ learnings: new LearningRepository(db2),
610
+ patterns: new PatternRepository(db2),
611
+ templates: new TemplateRepository(db2),
612
+ metrics: new MetricRepository(db2),
613
+ predictions: new PredictionRepository(db2)
614
+ };
615
+ }
616
+
617
+ // src/cli/index.ts
618
+ import { pathToFileURL } from "url";
619
+ var CLI = class {
620
+ commands = /* @__PURE__ */ new Map();
621
+ constructor() {
622
+ this.registerDefaultCommands();
623
+ }
624
+ registerCommand(cmd) {
625
+ this.commands.set(cmd.name, cmd);
626
+ }
627
+ async execute(args) {
628
+ const [cmd, ...rest] = args;
629
+ if (!cmd || cmd === "--help" || cmd === "-h") return this.showHelp();
630
+ const command = this.commands.get(cmd);
631
+ if (!command) return `Unknown command: ${cmd}. Use --help for available commands.`;
632
+ return command.execute(rest);
633
+ }
634
+ showHelp() {
635
+ const lines = ["Pi Missions CLI", "Usage: pi-missions <command> [options]", "", "Commands:"];
636
+ for (const [name, cmd] of this.commands) lines.push(` ${name.padEnd(15)} ${cmd.description}`);
637
+ return lines.join("\n");
638
+ }
639
+ registerDefaultCommands() {
640
+ this.registerCommand({ name: "list", description: "List all missions", execute: async () => {
641
+ const repos = getRepositories();
642
+ const missions = repos.missions.findAll(50);
643
+ if (!missions.length) return "No missions found.";
644
+ return missions.map((m) => `${m.id} [${m.status}] ${m.title}`).join("\n");
645
+ } });
646
+ this.registerCommand({ name: "status", description: "Show mission status", execute: async (args) => {
647
+ const repos = getRepositories();
648
+ const id = args[0];
649
+ if (!id) return "Usage: pi-missions status <mission-id>";
650
+ const m = repos.missions.findById(id);
651
+ if (!m) return `Mission not found: ${id}`;
652
+ const features = repos.features.findByMission(id);
653
+ const done = features.filter((f) => f.status === "done").length;
654
+ return `${m.title}
655
+ Status: ${m.status}
656
+ Progress: ${done}/${features.length}
657
+ Tokens: ${m.total_tokens}`;
658
+ } });
659
+ this.registerCommand({ name: "analytics", description: "Show analytics", execute: async () => {
660
+ const repos = getRepositories();
661
+ const missions = repos.missions.findAll(100);
662
+ const total = missions.length;
663
+ const completed = missions.filter((m) => m.status === "complete").length;
664
+ const active = missions.filter((m) => m.status === "active").length;
665
+ return `\u{1F4CA} Analytics
666
+ Total: ${total}
667
+ Active: ${active}
668
+ Completed: ${completed}
669
+ Success Rate: ${total > 0 ? Math.round(completed / total * 100) : 0}%`;
670
+ } });
671
+ this.registerCommand({ name: "templates", description: "List templates", execute: async () => {
672
+ const repos = getRepositories();
673
+ const templates = repos.templates.findAll();
674
+ return templates.map((t) => `${t.id.padEnd(15)} ${t.name} - ${t.description || ""}`).join("\n");
675
+ } });
676
+ this.registerCommand({ name: "history", description: "Show mission history", execute: async (args) => {
677
+ const repos = getRepositories();
678
+ const id = args[0];
679
+ if (!id) return "Usage: pi-missions history <mission-id>";
680
+ const history = repos.history.findByMission(id, 20);
681
+ if (!history.length) return "No history entries.";
682
+ return history.map((h) => `${new Date(h.timestamp).toISOString()} ${h.event} ${h.note || ""}`).join("\n");
683
+ } });
684
+ this.registerCommand({ name: "doctor", description: "Run diagnostics", execute: async () => {
685
+ const repos = getRepositories();
686
+ const missions = repos.missions.count();
687
+ const templates = repos.templates.findAll().length;
688
+ return `\u2705 Database: OK
689
+ \u{1F4CA} Missions: ${missions}
690
+ \u{1F4CB} Templates: ${templates}`;
691
+ } });
692
+ }
693
+ };
694
+ function createCLI() {
695
+ return new CLI();
696
+ }
697
+ function isDirectExecution() {
698
+ const entry = process.argv[1];
699
+ return Boolean(entry && import.meta.url === pathToFileURL(entry).href);
700
+ }
701
+ if (isDirectExecution()) {
702
+ createCLI().execute(process.argv.slice(2)).then((output) => {
703
+ if (output) console.log(output);
704
+ }).catch((error) => {
705
+ console.error(error instanceof Error ? error.message : String(error));
706
+ process.exitCode = 1;
707
+ });
708
+ }
709
+ export {
710
+ CLI,
711
+ createCLI
712
+ };
713
+ //# sourceMappingURL=index.js.map