@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.
- package/CHANGELOG.md +150 -0
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.js +713 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/database/schema.sql +322 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3970 -0
- package/dist/index.js.map +1 -0
- package/package.json +95 -0
|
@@ -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
|