@unpolarize/knowledge-planning 0.1.0 → 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,2020 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/schema/entities.ts
8
+ import { z } from "zod";
9
+ var SCHEMA_VERSION = 1;
10
+ var ns = (t) => `knowledge-planning/${t}@${SCHEMA_VERSION}`;
11
+ var OBJECT_TYPES = [
12
+ "domain",
13
+ "project",
14
+ "catalog_entry",
15
+ "idea",
16
+ "plan",
17
+ "task",
18
+ "daily_plan",
19
+ "reflection",
20
+ "insight",
21
+ "thought"
22
+ ];
23
+ var IDEA_STATUS = ["capture", "refine", "accepted", "plan", "parked", "done"];
24
+ var PLAN_STATUS = ["plan", "prototype", "implement", "validate", "done", "parked"];
25
+ var TASK_STATUS = ["inbox", "today", "in_progress", "done", "deferred", "outdated"];
26
+ var SESSION_PHASE = ["backlog", "planning", "implementing", "validating", "complete"];
27
+ var INSIGHT_STATUS = ["new", "accepted", "parked"];
28
+ var THOUGHT_STATUS = ["new", "kept", "converted", "archived"];
29
+ var KnowledgeRefSchema = z.object({
30
+ path: z.string().min(1),
31
+ reason: z.string().optional(),
32
+ status: z.enum(["open", "resolved"]).default("open"),
33
+ title: z.string().optional()
34
+ }).passthrough();
35
+ var baseShape = {
36
+ schema: z.string().optional(),
37
+ id: z.string().min(1),
38
+ title: z.string().optional(),
39
+ status: z.string().optional(),
40
+ created: z.string().optional(),
41
+ updated: z.string().optional(),
42
+ domain: z.string().optional(),
43
+ project: z.string().optional(),
44
+ priority: z.string().optional(),
45
+ tags: z.array(z.string()).default([]),
46
+ cites: z.array(z.string()).default([]),
47
+ blocked_by: z.array(KnowledgeRefSchema).default([]),
48
+ depends_on: z.array(z.string()).default([]),
49
+ related: z.array(z.string()).default([]),
50
+ linked_sessions: z.array(z.string()).default([])
51
+ };
52
+ var DomainSchema = z.object({ ...baseShape, type: z.literal("domain"), name: z.string().optional() }).passthrough();
53
+ var ProjectSchema = z.object({
54
+ ...baseShape,
55
+ type: z.literal("project"),
56
+ catalog_slug: z.string().optional(),
57
+ kb_paths: z.array(z.string()).default([]),
58
+ repos: z.array(z.string()).default([]),
59
+ status: z.string().default("active")
60
+ }).passthrough();
61
+ var CatalogEntrySchema = z.object({
62
+ ...baseShape,
63
+ type: z.literal("catalog_entry"),
64
+ catalog_slug: z.string().optional(),
65
+ kb_paths: z.array(z.string()).default([]),
66
+ status: z.string().default("active")
67
+ }).passthrough();
68
+ var IdeaSchema = z.object({
69
+ ...baseShape,
70
+ type: z.literal("idea"),
71
+ status: z.enum(IDEA_STATUS).default("capture"),
72
+ promoted_to: z.string().optional()
73
+ }).passthrough();
74
+ var PlanSchema = z.object({
75
+ ...baseShape,
76
+ type: z.literal("plan"),
77
+ status: z.enum(PLAN_STATUS).default("plan"),
78
+ kind: z.enum(["dev", "life"]).default("dev"),
79
+ phase: z.enum(SESSION_PHASE).optional(),
80
+ promoted_from: z.string().optional()
81
+ }).passthrough();
82
+ var TaskSchema = z.object({
83
+ ...baseShape,
84
+ type: z.literal("task"),
85
+ status: z.enum(TASK_STATUS).default("inbox"),
86
+ plan: z.string().optional(),
87
+ daily: z.string().optional(),
88
+ scheduled_for: z.string().optional(),
89
+ // Due date (YYYY-MM-DD). due === today ⇒ must-have for the day; due < today ⇒ overdue.
90
+ due: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
91
+ dispatch_task_id: z.string().optional()
92
+ }).passthrough();
93
+ var DailyPlanSchema = z.object({
94
+ ...baseShape,
95
+ type: z.literal("daily_plan"),
96
+ date: z.string().min(1),
97
+ domain_focus: z.array(z.string()).default([]),
98
+ carry_over_from: z.string().optional()
99
+ }).passthrough();
100
+ var ReflectionSchema = z.object({
101
+ ...baseShape,
102
+ type: z.literal("reflection"),
103
+ date: z.string().min(1),
104
+ daily_plan: z.string().optional(),
105
+ domains: z.array(z.string()).default([])
106
+ }).passthrough();
107
+ var InsightSchema = z.object({
108
+ ...baseShape,
109
+ type: z.literal("insight"),
110
+ source: z.string().min(1),
111
+ source_ref: z.string().optional(),
112
+ generated_at: z.string().optional(),
113
+ confidence: z.string().optional(),
114
+ suggested_tasks: z.array(z.string()).default([]),
115
+ status: z.enum(INSIGHT_STATUS).default("new")
116
+ }).passthrough();
117
+ var ThoughtSchema = z.object({
118
+ ...baseShape,
119
+ type: z.literal("thought"),
120
+ status: z.enum(THOUGHT_STATUS).default("new"),
121
+ // provenance — where the prose came from and when
122
+ source: z.string().optional(),
123
+ // provenance, e.g. 'gdoc:<doc-slug>'
124
+ source_url: z.string().optional(),
125
+ surfaced_on: z.string().optional(),
126
+ // YYYY-MM-DD the ingest saw it
127
+ context: z.string().optional(),
128
+ // date header / section the prose sat under
129
+ converted_to: z.string().optional()
130
+ // idea id after conversion
131
+ }).passthrough();
132
+ var SCHEMA_BY_TYPE = {
133
+ domain: DomainSchema,
134
+ project: ProjectSchema,
135
+ catalog_entry: CatalogEntrySchema,
136
+ idea: IdeaSchema,
137
+ plan: PlanSchema,
138
+ task: TaskSchema,
139
+ daily_plan: DailyPlanSchema,
140
+ reflection: ReflectionSchema,
141
+ insight: InsightSchema,
142
+ thought: ThoughtSchema
143
+ };
144
+ var ObjectSchema = z.discriminatedUnion("type", [
145
+ DomainSchema,
146
+ ProjectSchema,
147
+ CatalogEntrySchema,
148
+ IdeaSchema,
149
+ PlanSchema,
150
+ TaskSchema,
151
+ DailyPlanSchema,
152
+ ReflectionSchema,
153
+ InsightSchema,
154
+ ThoughtSchema
155
+ ]);
156
+ var EDGE_KINDS = [
157
+ "depends_on",
158
+ "blocks",
159
+ "blocked_by",
160
+ "implements",
161
+ "cites",
162
+ "translates_to",
163
+ "promoted_to",
164
+ "parent_of",
165
+ "related",
166
+ "evidence",
167
+ "surfaced_on",
168
+ "scheduled_for"
169
+ ];
170
+ var EdgeSchema = z.object({
171
+ kind: z.enum(EDGE_KINDS),
172
+ from: z.string().min(1),
173
+ to: z.string().min(1),
174
+ reason: z.string().optional(),
175
+ status: z.enum(["open", "resolved"]).optional(),
176
+ created_at: z.string().optional()
177
+ }).passthrough();
178
+ function parseObject(data) {
179
+ return ObjectSchema.parse(data);
180
+ }
181
+ function safeParseObject(data) {
182
+ return ObjectSchema.safeParse(data);
183
+ }
184
+
185
+ // src/schema/frontmatter.ts
186
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
187
+ var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
188
+ function parseMarkdown(content) {
189
+ const m = content.match(FRONTMATTER_RE);
190
+ if (!m) return { data: {}, body: content };
191
+ const parsed = parseYaml(m[1]) ?? {};
192
+ const data = typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
193
+ return { data, body: content.slice(m[0].length) };
194
+ }
195
+ function clean(value) {
196
+ if (Array.isArray(value)) return value.map(clean).filter((v) => v !== void 0 && v !== null);
197
+ if (value && typeof value === "object") {
198
+ const out = {};
199
+ for (const [k, v] of Object.entries(value)) {
200
+ const cv = clean(v);
201
+ if (cv === void 0 || cv === null) continue;
202
+ if (Array.isArray(cv) && cv.length === 0) continue;
203
+ out[k] = cv;
204
+ }
205
+ return out;
206
+ }
207
+ return value;
208
+ }
209
+ function serializeMarkdown(data, body = "") {
210
+ const cleaned = clean(data);
211
+ const fm = stringifyYaml(cleaned, { lineWidth: 0 }).trimEnd();
212
+ const block = `---
213
+ ${fm}
214
+ ---
215
+ `;
216
+ const trimmed = body.replace(/^\n+/, "");
217
+ if (!trimmed.trim()) return block;
218
+ return `${block}
219
+ ${trimmed.replace(/\n*$/, "\n")}`;
220
+ }
221
+
222
+ // src/types.ts
223
+ var DEFAULT_CONFIG = {
224
+ domains: [],
225
+ kb_root: process.env.KP_KB_ROOT ?? process.cwd(),
226
+ sessions_root: process.env.CODE_SESSIONS_STORE ?? "",
227
+ project_rules: [],
228
+ project_id_rules: []
229
+ };
230
+
231
+ // src/store/store.ts
232
+ import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync, appendFileSync, rmSync } from "fs";
233
+ import { join, dirname, relative } from "path";
234
+ var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".index", "src", "test", "dist", ".vscode"]);
235
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set(["project"]);
236
+ var Store = class {
237
+ root;
238
+ docs = null;
239
+ warnings = [];
240
+ constructor(root) {
241
+ this.root = root;
242
+ }
243
+ get configPath() {
244
+ return join(this.root, "config.json");
245
+ }
246
+ get edgesPath() {
247
+ return join(this.root, "edges.jsonl");
248
+ }
249
+ config() {
250
+ if (!existsSync(this.configPath)) return { ...DEFAULT_CONFIG };
251
+ try {
252
+ return { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync(this.configPath, "utf8")) };
253
+ } catch {
254
+ return { ...DEFAULT_CONFIG };
255
+ }
256
+ }
257
+ /** Force a re-scan on next access. */
258
+ invalidate() {
259
+ this.docs = null;
260
+ }
261
+ ensureLoaded() {
262
+ if (this.docs) return this.docs;
263
+ const docs = /* @__PURE__ */ new Map();
264
+ this.warnings = [];
265
+ for (const abs of walkMarkdown(this.root)) {
266
+ let raw;
267
+ try {
268
+ raw = readFileSync(abs, "utf8");
269
+ } catch {
270
+ continue;
271
+ }
272
+ const { data, body } = parseMarkdown(raw);
273
+ const type = data.type;
274
+ const id = data.id;
275
+ if (!type || !id || !(type in SCHEMA_BY_TYPE)) continue;
276
+ const schema = SCHEMA_BY_TYPE[type];
277
+ const res = schema.safeParse(data);
278
+ if (!res.success) {
279
+ this.warnings.push(`${relative(this.root, abs)}: ${res.error.issues[0]?.message ?? "invalid"}`);
280
+ continue;
281
+ }
282
+ docs.set(id, {
283
+ id,
284
+ type,
285
+ path: abs,
286
+ relpath: relative(this.root, abs),
287
+ object: res.data,
288
+ data,
289
+ body
290
+ });
291
+ }
292
+ this.docs = docs;
293
+ return docs;
294
+ }
295
+ getWarnings() {
296
+ this.ensureLoaded();
297
+ return this.warnings;
298
+ }
299
+ all() {
300
+ return [...this.ensureLoaded().values()];
301
+ }
302
+ get(id) {
303
+ return this.ensureLoaded().get(id);
304
+ }
305
+ byType(type) {
306
+ return this.all().filter((d) => d.type === type);
307
+ }
308
+ /** Default on-disk path for a new object id. Projects are folders (`<id>/index.md`). */
309
+ pathForId(id, type) {
310
+ if (CONTAINER_TYPES.has(type)) return join(this.root, id, "index.md");
311
+ return join(this.root, `${id}.md`);
312
+ }
313
+ /**
314
+ * Create or overwrite an object. Frontmatter is validated; body defaults to the
315
+ * existing body when omitted for an existing object.
316
+ */
317
+ write(input, body) {
318
+ const { id, type } = input;
319
+ if (!(type in SCHEMA_BY_TYPE)) throw new Error(`unknown type: ${type}`);
320
+ const existing = this.get(id);
321
+ const data = { schema: ns(type), ...input };
322
+ const schema = SCHEMA_BY_TYPE[type];
323
+ const parsed = schema.parse(data);
324
+ const path2 = existing?.path ?? this.pathForId(id, type);
325
+ const finalBody = body ?? existing?.body ?? "";
326
+ mkdirSync(dirname(path2), { recursive: true });
327
+ writeFileSync(path2, serializeMarkdown(data, finalBody), "utf8");
328
+ const doc = {
329
+ id,
330
+ type,
331
+ path: path2,
332
+ relpath: relative(this.root, path2),
333
+ object: parsed,
334
+ data,
335
+ body: finalBody
336
+ };
337
+ this.ensureLoaded().set(id, doc);
338
+ return doc;
339
+ }
340
+ /** Load an object, mutate its frontmatter via `mutator`, and write it back. */
341
+ patch(id, mutator) {
342
+ const existing = this.get(id);
343
+ if (!existing) throw new Error(`object not found: ${id}`);
344
+ const data = { ...existing.data };
345
+ mutator(data);
346
+ return this.write(data, existing.body);
347
+ }
348
+ /** Delete an object: remove its file (folder for container types), drop it from
349
+ * the loaded map, and prune any edges.jsonl rows that reference it. */
350
+ delete(id) {
351
+ const existing = this.get(id);
352
+ if (!existing) return false;
353
+ if (CONTAINER_TYPES.has(existing.type)) {
354
+ rmSync(dirname(existing.path), { recursive: true, force: true });
355
+ } else {
356
+ rmSync(existing.path, { force: true });
357
+ }
358
+ this.ensureLoaded().delete(id);
359
+ if (existsSync(this.edgesPath)) {
360
+ const kept = this.fileEdges().filter((e) => e.from !== id && e.to !== id);
361
+ writeFileSync(this.edgesPath, kept.map((e) => JSON.stringify(e)).join("\n") + (kept.length ? "\n" : ""), "utf8");
362
+ }
363
+ return true;
364
+ }
365
+ // --- Edges ---------------------------------------------------------------
366
+ appendEdge(edge) {
367
+ const e = EdgeSchema.parse(edge);
368
+ appendFileSync(this.edgesPath, JSON.stringify(e) + "\n", "utf8");
369
+ }
370
+ /** Explicit edges from edges.jsonl (append-only log). */
371
+ fileEdges() {
372
+ if (!existsSync(this.edgesPath)) return [];
373
+ const out = [];
374
+ for (const line of readFileSync(this.edgesPath, "utf8").split("\n")) {
375
+ const t = line.trim();
376
+ if (!t) continue;
377
+ try {
378
+ out.push(EdgeSchema.parse(JSON.parse(t)));
379
+ } catch {
380
+ }
381
+ }
382
+ return out;
383
+ }
384
+ /** Edges derived from object frontmatter (the rebuildable source of truth). */
385
+ frontmatterEdges() {
386
+ const edges = [];
387
+ for (const d of this.all()) {
388
+ const o = d.object;
389
+ for (const ref of o.blocked_by ?? [])
390
+ edges.push({ kind: "blocked_by", from: o.id, to: ref.path, reason: ref.reason, status: ref.status });
391
+ for (const c of o.cites ?? []) edges.push({ kind: "cites", from: o.id, to: c });
392
+ for (const dep of o.depends_on ?? []) edges.push({ kind: "depends_on", from: o.id, to: dep });
393
+ for (const r of o.related ?? []) edges.push({ kind: "related", from: o.id, to: r });
394
+ for (const s of o.linked_sessions ?? []) edges.push({ kind: "evidence", from: `session:${s}`, to: o.id });
395
+ if (o.type === "idea" && o.promoted_to) edges.push({ kind: "promoted_to", from: o.id, to: o.promoted_to });
396
+ if (o.type === "task" && o.plan) edges.push({ kind: "parent_of", from: o.plan, to: o.id });
397
+ if (o.type === "task" && o.scheduled_for) edges.push({ kind: "scheduled_for", from: o.id, to: o.scheduled_for });
398
+ if (o.type === "insight") for (const t of o.suggested_tasks ?? []) edges.push({ kind: "surfaced_on", from: o.id, to: t });
399
+ if (o.project) edges.push({ kind: "parent_of", from: o.project, to: o.id });
400
+ }
401
+ return edges;
402
+ }
403
+ /** Union of frontmatter-derived and explicit edges. */
404
+ allEdges() {
405
+ return [...this.frontmatterEdges(), ...this.fileEdges()];
406
+ }
407
+ };
408
+ function walkMarkdown(root) {
409
+ const out = [];
410
+ if (!existsSync(root)) return out;
411
+ const walk = (dir) => {
412
+ let entries;
413
+ try {
414
+ entries = readdirSync(dir, { withFileTypes: true });
415
+ } catch {
416
+ return;
417
+ }
418
+ for (const e of entries) {
419
+ if (e.isDirectory()) {
420
+ if (IGNORE_DIRS.has(e.name) || e.name.startsWith(".")) continue;
421
+ walk(join(dir, e.name));
422
+ } else if (e.isFile() && e.name.endsWith(".md")) {
423
+ out.push(join(dir, e.name));
424
+ }
425
+ }
426
+ };
427
+ walk(root);
428
+ return out;
429
+ }
430
+
431
+ // src/kb-bridge/kb-bridge.ts
432
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
433
+ import { isAbsolute, join as join2 } from "path";
434
+ function resolveKnowledgeRef(refPath, kbRoot) {
435
+ return isAbsolute(refPath) ? refPath : join2(kbRoot, refPath);
436
+ }
437
+ function readTitle(absPath) {
438
+ if (!existsSync2(absPath)) return void 0;
439
+ const { data, body } = parseMarkdown(readFileSync2(absPath, "utf8"));
440
+ if (typeof data.title === "string") return data.title;
441
+ if (Array.isArray(data.aliases) && typeof data.aliases[0] === "string") return data.aliases[0];
442
+ const m = body.match(/^#\s+(.+)$/m);
443
+ return m ? m[1].trim() : void 0;
444
+ }
445
+ function hasDecision(absPath) {
446
+ if (!existsSync2(absPath)) return false;
447
+ const { data, body } = parseMarkdown(readFileSync2(absPath, "utf8"));
448
+ if (data.decision != null && String(data.decision).trim() !== "") return true;
449
+ return /^#{1,6}\s+decision\b/im.test(body);
450
+ }
451
+ function inspectKnowledgeRef(ownerId, ref, kbRoot) {
452
+ const abs = resolveKnowledgeRef(ref.path, kbRoot);
453
+ const exists = existsSync2(abs);
454
+ const decided = exists && hasDecision(abs);
455
+ const status = ref.status ?? "open";
456
+ return {
457
+ id: ownerId,
458
+ path: ref.path,
459
+ abs,
460
+ exists,
461
+ decided,
462
+ status,
463
+ reason: ref.reason,
464
+ title: exists ? readTitle(abs) : void 0,
465
+ resolvable: decided && status !== "resolved"
466
+ };
467
+ }
468
+
469
+ // src/index/db.ts
470
+ import { mkdirSync as mkdirSync2 } from "fs";
471
+ import { dirname as dirname2 } from "path";
472
+ var getBuiltinModule = globalThis.process.getBuiltinModule;
473
+ var { DatabaseSync } = getBuiltinModule("node:sqlite");
474
+ var PlanningIndex = class {
475
+ db;
476
+ fts;
477
+ store;
478
+ constructor(store, dbPath = ":memory:") {
479
+ this.store = store;
480
+ if (dbPath !== ":memory:") mkdirSync2(dirname2(dbPath), { recursive: true });
481
+ this.db = new DatabaseSync(dbPath);
482
+ this.fts = this.detectFts();
483
+ this.build();
484
+ }
485
+ detectFts() {
486
+ try {
487
+ this.db.exec("CREATE VIRTUAL TABLE __fts_probe USING fts5(x); DROP TABLE __fts_probe;");
488
+ return true;
489
+ } catch {
490
+ return false;
491
+ }
492
+ }
493
+ build() {
494
+ this.db.exec(`
495
+ DROP TABLE IF EXISTS objects;
496
+ DROP TABLE IF EXISTS edges;
497
+ DROP TABLE IF EXISTS objects_fts;
498
+ CREATE TABLE objects (
499
+ id TEXT PRIMARY KEY, type TEXT, title TEXT, status TEXT,
500
+ domain TEXT, project TEXT, path TEXT, updated TEXT,
501
+ priority TEXT, due TEXT, created TEXT, surfaced_on TEXT, context TEXT,
502
+ linked_sessions TEXT
503
+ );
504
+ CREATE TABLE edges (kind TEXT, from_id TEXT, to_id TEXT, reason TEXT, status TEXT);
505
+ CREATE INDEX idx_obj_type ON objects(type);
506
+ CREATE INDEX idx_obj_status ON objects(status);
507
+ CREATE INDEX idx_edge_kind ON edges(kind);
508
+ `);
509
+ if (this.fts) this.db.exec("CREATE VIRTUAL TABLE objects_fts USING fts5(id UNINDEXED, title, body);");
510
+ const insObj = this.db.prepare(
511
+ "INSERT OR REPLACE INTO objects (id,type,title,status,domain,project,path,updated,priority,due,created,surfaced_on,context,linked_sessions) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
512
+ );
513
+ const insFts = this.fts ? this.db.prepare("INSERT INTO objects_fts (id,title,body) VALUES (?,?,?)") : null;
514
+ for (const d of this.store.all()) {
515
+ const o = d.object;
516
+ insObj.run(
517
+ d.id,
518
+ d.type,
519
+ o.title ?? null,
520
+ o.status ?? null,
521
+ o.domain ?? null,
522
+ o.project ?? null,
523
+ d.relpath,
524
+ o.updated ?? null,
525
+ o.priority ?? null,
526
+ o.due ?? null,
527
+ o.created ?? null,
528
+ o.surfaced_on ?? null,
529
+ o.context ?? null,
530
+ Array.isArray(o.linked_sessions) && o.linked_sessions.length ? JSON.stringify(o.linked_sessions) : null
531
+ );
532
+ insFts?.run(d.id, o.title ?? "", d.body ?? "");
533
+ }
534
+ const insEdge = this.db.prepare("INSERT INTO edges (kind,from_id,to_id,reason,status) VALUES (?,?,?,?,?)");
535
+ for (const e of this.store.allEdges()) insEdge.run(e.kind, e.from, e.to, e.reason ?? null, e.status ?? null);
536
+ }
537
+ close() {
538
+ this.db.close();
539
+ }
540
+ // --- Queries -------------------------------------------------------------
541
+ all() {
542
+ return this.db.prepare("SELECT * FROM objects ORDER BY type, id").all();
543
+ }
544
+ byType(type) {
545
+ return this.db.prepare("SELECT * FROM objects WHERE type = ? ORDER BY id").all(type);
546
+ }
547
+ listByStatus(status) {
548
+ return this.db.prepare("SELECT * FROM objects WHERE status = ? ORDER BY type, id").all(status);
549
+ }
550
+ counts() {
551
+ const rows = this.db.prepare("SELECT type, COUNT(*) n FROM objects GROUP BY type").all();
552
+ return Object.fromEntries(rows.map((r) => [r.type, r.n]));
553
+ }
554
+ /** All `blocked_by` knowledge edges, inspected against the KB for decision status. */
555
+ blockedByKnowledge(kbRoot) {
556
+ const out = [];
557
+ for (const d of this.store.all()) {
558
+ for (const ref of d.object.blocked_by ?? []) out.push(inspectKnowledgeRef(d.id, ref, kbRoot));
559
+ }
560
+ return out;
561
+ }
562
+ /** Tasks linked to a day, grouped by lane, plus the daily plan body. */
563
+ dailyBoard(date) {
564
+ const dailyId = `daily/${date}`;
565
+ const daily = this.store.get(dailyId);
566
+ const tasks = this.store.byType("task").filter((t) => {
567
+ const o = t.object;
568
+ return o.daily === dailyId || o.scheduled_for === dailyId;
569
+ }).map((t) => this.rowOf(t.id)).filter((r) => !!r);
570
+ const lanes = {};
571
+ for (const lane of TASK_STATUS) lanes[lane] = [];
572
+ for (const t of tasks) (lanes[t.status ?? "inbox"] ??= []).push(t);
573
+ return {
574
+ date,
575
+ daily_id: daily ? dailyId : null,
576
+ body: daily?.body ?? "",
577
+ lanes,
578
+ due_today: this.dueTasks(date, date),
579
+ overdue: this.dueTasks(null, date, true)
580
+ };
581
+ }
582
+ /** Open (not done/outdated) tasks with a due date in [from, to]; strict=true means due < to. */
583
+ dueTasks(from, to, strict = false) {
584
+ const cmp = strict ? "due < ?" : from ? "due >= ? AND due <= ?" : "due <= ?";
585
+ const params = strict ? [to] : from ? [from, to] : [to];
586
+ return this.db.prepare(
587
+ `SELECT * FROM objects WHERE type='task' AND due IS NOT NULL AND status NOT IN ('done','outdated') AND ${cmp} ORDER BY due, priority`
588
+ ).all(...params);
589
+ }
590
+ /** Tasks with due dates in a window, sorted by due then priority — the calendar view. */
591
+ calendar(from, to) {
592
+ return this.db.prepare(
593
+ "SELECT * FROM objects WHERE type='task' AND due IS NOT NULL AND due >= ? AND due <= ? ORDER BY due, priority, id"
594
+ ).all(from, to);
595
+ }
596
+ rowOf(id) {
597
+ return this.db.prepare("SELECT * FROM objects WHERE id = ?").get(id);
598
+ }
599
+ search(q) {
600
+ if (this.fts) {
601
+ try {
602
+ const ids = this.db.prepare("SELECT id FROM objects_fts WHERE objects_fts MATCH ? LIMIT 50").all(q);
603
+ if (ids.length === 0) return [];
604
+ const placeholders = ids.map(() => "?").join(",");
605
+ return this.db.prepare(`SELECT * FROM objects WHERE id IN (${placeholders})`).all(...ids.map((r) => r.id));
606
+ } catch {
607
+ }
608
+ }
609
+ const like = `%${q}%`;
610
+ return this.db.prepare("SELECT * FROM objects WHERE title LIKE ? OR id LIKE ? ORDER BY type, id LIMIT 50").all(like, like);
611
+ }
612
+ };
613
+ function persistIndex(store, dbPath) {
614
+ const idx = new PlanningIndex(store, dbPath);
615
+ const objects = store.all().length;
616
+ const edges = store.allEdges().length;
617
+ idx.close();
618
+ return { objects, edges };
619
+ }
620
+
621
+ // src/graph/graph.ts
622
+ import { existsSync as existsSync3 } from "fs";
623
+ function buildGraph(store, kbRoot) {
624
+ const nodes = /* @__PURE__ */ new Map();
625
+ const edges = [];
626
+ const add = (n) => {
627
+ if (!nodes.has(n.id)) nodes.set(n.id, n);
628
+ };
629
+ for (const d of store.all()) {
630
+ const o = d.object;
631
+ add({
632
+ id: d.id,
633
+ label: o.title ?? d.id,
634
+ type: d.type,
635
+ group: o.domain ?? o.project ?? d.type,
636
+ status: o.status ?? void 0
637
+ });
638
+ }
639
+ for (const e of store.allEdges()) {
640
+ if (e.kind === "blocked_by" || e.kind === "cites" || e.kind === "implements") {
641
+ const abs = resolveKnowledgeRef(e.to, kbRoot);
642
+ const exists = existsSync3(abs);
643
+ add({
644
+ id: e.to,
645
+ label: (exists ? readTitle(abs) : void 0) ?? e.to.split("/").pop() ?? e.to,
646
+ type: "knowledge",
647
+ group: "knowledge",
648
+ blocked: e.kind === "blocked_by" && e.status !== "resolved"
649
+ });
650
+ }
651
+ if (e.kind === "evidence" && e.from.startsWith("session:")) {
652
+ add({ id: e.from, label: e.from.replace("session:", "").slice(0, 8), type: "session", group: "sessions" });
653
+ }
654
+ edges.push({ from: e.from, to: e.to, kind: e.kind, status: e.status });
655
+ }
656
+ return { nodes: [...nodes.values()], edges };
657
+ }
658
+
659
+ // src/migrate/gdoc.ts
660
+ var DATE_HEADER = /^(Sun|Mon|Tue|Wed|Thu|Fri|Sat),?\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}(,\s+\d{4})?$/i;
661
+ function nextContext(prev, headingText) {
662
+ const t = headingText.trim();
663
+ if (DATE_HEADER.test(t)) return t;
664
+ return prev ? `${prev} \u203A ${t}` : t;
665
+ }
666
+ function extractBullets(text) {
667
+ const out = [];
668
+ let context;
669
+ for (const line of text.split("\n")) {
670
+ const t = line.trim();
671
+ if (DATE_HEADER.test(t)) {
672
+ context = t;
673
+ continue;
674
+ }
675
+ const h = t.match(/^#{1,6}\s+(.+)$/);
676
+ if (h) {
677
+ context = nextContext(context, h[1]);
678
+ continue;
679
+ }
680
+ const m = line.match(/^(\s*)[-*+]\s+(\[([ xX])\]\s+)?(.+?)\s*$/);
681
+ if (!m) continue;
682
+ const indent = m[1].replace(/\t/g, " ").length;
683
+ const checked = m[3] !== void 0 ? /[xX]/.test(m[3]) : void 0;
684
+ out.push({ text: m[4].trim(), checked, depth: Math.floor(indent / 2), context });
685
+ }
686
+ return out;
687
+ }
688
+ function extractProse(text, minLen = 60) {
689
+ const out = [];
690
+ let context;
691
+ let buf = [];
692
+ const flush = () => {
693
+ const t = buf.join(" ").replace(/\s+/g, " ").trim();
694
+ buf = [];
695
+ if (t.length >= minLen && !/^https?:\/\/\S+$/.test(t)) out.push({ text: t, context });
696
+ };
697
+ for (const line of text.split("\n")) {
698
+ const t = line.trim();
699
+ if (!t) {
700
+ flush();
701
+ continue;
702
+ }
703
+ if (DATE_HEADER.test(t)) {
704
+ flush();
705
+ context = t;
706
+ continue;
707
+ }
708
+ const h = t.match(/^#{1,6}\s+(.+)$/);
709
+ if (h) {
710
+ flush();
711
+ context = nextContext(context, h[1]);
712
+ continue;
713
+ }
714
+ if (/^(\s*)[-*+]\s+/.test(line) || /^\d+\.\s+/.test(t)) {
715
+ flush();
716
+ continue;
717
+ }
718
+ buf.push(t);
719
+ }
720
+ flush();
721
+ return out;
722
+ }
723
+ function slugify(s) {
724
+ return s.toLowerCase().replace(/[`*_~]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "item";
725
+ }
726
+ function toSeeds(bullets, opts = {}) {
727
+ const seeds = [];
728
+ const seen = /* @__PURE__ */ new Set();
729
+ for (const b of bullets) {
730
+ if (!b.text || b.text.length < 3) continue;
731
+ const isTask = b.checked !== void 0;
732
+ const base = `${isTask ? "tasks" : "ideas"}/${slugify(b.text)}`;
733
+ let id = base;
734
+ let n = 2;
735
+ while (seen.has(id)) id = `${base}-${n++}`;
736
+ seen.add(id);
737
+ seeds.push({
738
+ id,
739
+ type: isTask ? "task" : "idea",
740
+ title: b.text,
741
+ status: isTask ? b.checked ? "done" : "inbox" : "capture",
742
+ domain: opts.domain,
743
+ source: opts.source,
744
+ context: b.context
745
+ });
746
+ }
747
+ return seeds;
748
+ }
749
+
750
+ // src/cli/sessions.ts
751
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
752
+ import { join as join3 } from "path";
753
+ function findSessionEnvelope(sessionsRoot, uuid) {
754
+ const hostsDir = join3(sessionsRoot, "hosts");
755
+ if (!existsSync4(hostsDir)) return void 0;
756
+ for (const host of safeDirs(hostsDir)) {
757
+ const hostDir = join3(hostsDir, host);
758
+ for (const month of safeDirs(hostDir)) {
759
+ const candidate = join3(hostDir, month, uuid, "session.json");
760
+ if (existsSync4(candidate)) return candidate;
761
+ }
762
+ }
763
+ return void 0;
764
+ }
765
+ function safeDirs(dir) {
766
+ try {
767
+ return readdirSync2(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
768
+ } catch {
769
+ return [];
770
+ }
771
+ }
772
+ function addPlanningRef(envelopePath, planningId) {
773
+ const env = JSON.parse(readFileSync3(envelopePath, "utf8"));
774
+ const refs = new Set(env.planning_refs ?? []);
775
+ if (refs.has(planningId)) return false;
776
+ refs.add(planningId);
777
+ env.planning_refs = [...refs];
778
+ writeFileSync2(envelopePath, JSON.stringify(env, null, 2) + "\n", "utf8");
779
+ return true;
780
+ }
781
+
782
+ // src/sessions/reader.ts
783
+ import * as fs from "fs";
784
+ import * as os from "os";
785
+ import * as path from "path";
786
+ function sessionsGitRoot() {
787
+ return process.env.CODE_SESSIONS_STORE || path.join(os.homedir(), ".sessions");
788
+ }
789
+ function nativeClaudeRoot() {
790
+ return process.env.CODE_SESSIONS_NATIVE_ROOT || path.join(os.homedir(), ".claude", "projects");
791
+ }
792
+ var localDay = (iso, fallbackMs) => {
793
+ const d = iso ? new Date(iso) : fallbackMs ? new Date(fallbackMs) : void 0;
794
+ if (!d || isNaN(d.getTime())) return "";
795
+ const y = d.getFullYear();
796
+ const m = String(d.getMonth() + 1).padStart(2, "0");
797
+ const dd = String(d.getDate()).padStart(2, "0");
798
+ return `${y}-${m}-${dd}`;
799
+ };
800
+ function cleanTitle(raw) {
801
+ return raw.replace(/<command-[^>]*>[\s\S]*?<\/command-[^>]*>/g, " ").replace(/<local-command-[^>]*>[\s\S]*?(<\/local-command-[^>]*>|$)/g, " ").replace(/<system-reminder>[\s\S]*?(<\/system-reminder>|$)/g, " ").replace(/<[^>\n]{1,40}>/g, " ").replace(/\s+/g, " ").trim().slice(0, 140);
802
+ }
803
+ function readGitStoreSessions(root = sessionsGitRoot()) {
804
+ const out = [];
805
+ const hostsDir = path.join(root, "hosts");
806
+ let hosts = [];
807
+ try {
808
+ hosts = fs.readdirSync(hostsDir);
809
+ } catch {
810
+ return out;
811
+ }
812
+ for (const host of hosts) {
813
+ let months = [];
814
+ try {
815
+ months = fs.readdirSync(path.join(hostsDir, host));
816
+ } catch {
817
+ continue;
818
+ }
819
+ for (const month of months) {
820
+ const monthDir = path.join(hostsDir, host, month);
821
+ let ids = [];
822
+ try {
823
+ ids = fs.readdirSync(monthDir);
824
+ } catch {
825
+ continue;
826
+ }
827
+ for (const id of ids) {
828
+ const f = path.join(monthDir, id, "session.json");
829
+ try {
830
+ const d = JSON.parse(fs.readFileSync(f, "utf8"));
831
+ const started = d.started_at;
832
+ const ended = d.ended_at;
833
+ const labels = d.labels ?? [];
834
+ const excerpt = labels.find((l) => !l.startsWith("intent:"));
835
+ const projectPath = d.project_path || void 0;
836
+ out.push({
837
+ id: d.session_id || id,
838
+ day: localDay(started),
839
+ started_at: started,
840
+ ended_at: ended,
841
+ minutes: started && ended ? Math.max(0, Math.round((+new Date(ended) - +new Date(started)) / 6e4)) : void 0,
842
+ agent: d.agent || "unknown",
843
+ host: d.host,
844
+ project_path: projectPath,
845
+ project_name: projectPath ? path.basename(projectPath) : void 0,
846
+ title: excerpt ? cleanTitle(excerpt) : void 0,
847
+ turns: d.turn_count,
848
+ source: "git"
849
+ });
850
+ } catch {
851
+ }
852
+ }
853
+ }
854
+ }
855
+ return out;
856
+ }
857
+ function decodeNativeDir(name) {
858
+ return name.replace(/^-/, "/").replace(/-/g, "/");
859
+ }
860
+ function readNativeClaudeSessions(root = nativeClaudeRoot(), headBytes = 32 * 1024) {
861
+ const out = [];
862
+ let dirs = [];
863
+ try {
864
+ dirs = fs.readdirSync(root);
865
+ } catch {
866
+ return out;
867
+ }
868
+ for (const dir of dirs) {
869
+ const abs = path.join(root, dir);
870
+ let files = [];
871
+ try {
872
+ if (!fs.statSync(abs).isDirectory()) continue;
873
+ files = fs.readdirSync(abs).filter((f) => f.endsWith(".jsonl"));
874
+ } catch {
875
+ continue;
876
+ }
877
+ for (const file of files) {
878
+ const fp = path.join(abs, file);
879
+ try {
880
+ const stat = fs.statSync(fp);
881
+ if (stat.size === 0) continue;
882
+ const fd = fs.openSync(fp, "r");
883
+ const buf = Buffer.alloc(Math.min(headBytes, stat.size));
884
+ fs.readSync(fd, buf, 0, buf.length, 0);
885
+ fs.closeSync(fd);
886
+ const headLines = buf.toString("utf8").split("\n");
887
+ let title;
888
+ let firstTs;
889
+ for (const line of headLines) {
890
+ if (!line.trim()) continue;
891
+ let obj;
892
+ try {
893
+ obj = JSON.parse(line);
894
+ } catch {
895
+ continue;
896
+ }
897
+ if (!firstTs && typeof obj.timestamp === "string") firstTs = obj.timestamp;
898
+ if (!title && obj.type === "summary" && typeof obj.summary === "string") {
899
+ title = cleanTitle(obj.summary);
900
+ }
901
+ if (!title && obj.type === "user") {
902
+ const msg = obj.message;
903
+ const content = msg?.content;
904
+ let text = "";
905
+ if (typeof content === "string") text = content;
906
+ else if (Array.isArray(content)) {
907
+ const t = content.find((c) => c.type === "text");
908
+ text = t?.text ?? "";
909
+ }
910
+ const cleaned = cleanTitle(text);
911
+ if (cleaned) title = cleaned;
912
+ }
913
+ if (title && firstTs) break;
914
+ }
915
+ const projectPath = decodeNativeDir(dir);
916
+ out.push({
917
+ id: path.basename(file, ".jsonl"),
918
+ day: localDay(firstTs, stat.mtimeMs),
919
+ started_at: firstTs,
920
+ ended_at: new Date(stat.mtimeMs).toISOString(),
921
+ minutes: firstTs ? Math.max(0, Math.round((stat.mtimeMs - +new Date(firstTs)) / 6e4)) : void 0,
922
+ agent: "claude-code",
923
+ project_path: projectPath,
924
+ project_name: path.basename(projectPath),
925
+ title,
926
+ source: "native"
927
+ });
928
+ } catch {
929
+ }
930
+ }
931
+ }
932
+ return out;
933
+ }
934
+ function readAllSessions() {
935
+ const byId = /* @__PURE__ */ new Map();
936
+ for (const s of readGitStoreSessions()) byId.set(s.id, s);
937
+ for (const s of readNativeClaudeSessions()) {
938
+ const prev = byId.get(s.id);
939
+ if (prev) {
940
+ byId.set(s.id, {
941
+ ...prev,
942
+ ...s,
943
+ title: s.title || prev.title,
944
+ host: prev.host,
945
+ turns: prev.turns ?? s.turns,
946
+ source: "both"
947
+ });
948
+ } else {
949
+ byId.set(s.id, s);
950
+ }
951
+ }
952
+ return [...byId.values()].filter((s) => s.day).sort((a, b) => (a.started_at ?? "").localeCompare(b.started_at ?? ""));
953
+ }
954
+ function sessionsByDay(sessions, from, to) {
955
+ const days = {};
956
+ for (const s of sessions) {
957
+ if (s.day < from || s.day > to) continue;
958
+ (days[s.day] ??= []).push(s);
959
+ }
960
+ return days;
961
+ }
962
+
963
+ // src/cli/commands.ts
964
+ var commands_exports = {};
965
+ __export(commands_exports, {
966
+ CLOSING_STATUSES: () => CLOSING_STATUSES,
967
+ DEFAULT_GROUPS: () => DEFAULT_GROUPS,
968
+ buildLookback: () => buildLookback,
969
+ buildTopics: () => buildTopics,
970
+ cmdBlocked: () => cmdBlocked,
971
+ cmdCalendar: () => cmdCalendar,
972
+ cmdCapture: () => cmdCapture,
973
+ cmdCreate: () => cmdCreate,
974
+ cmdDaily: () => cmdDaily,
975
+ cmdDelete: () => cmdDelete,
976
+ cmdDeriveProjects: () => cmdDeriveProjects,
977
+ cmdDeriveSessions: () => cmdDeriveSessions,
978
+ cmdDoctor: () => cmdDoctor,
979
+ cmdEdit: () => cmdEdit,
980
+ cmdEditObject: () => cmdEditObject,
981
+ cmdExport: () => cmdExport,
982
+ cmdGraph: () => cmdGraph,
983
+ cmdIngestGdoc: () => cmdIngestGdoc,
984
+ cmdIngestThoughts: () => cmdIngestThoughts,
985
+ cmdInsightIngest: () => cmdInsightIngest,
986
+ cmdLabelSessions: () => cmdLabelSessions,
987
+ cmdLinkSession: () => cmdLinkSession,
988
+ cmdList: () => cmdList,
989
+ cmdLookback: () => cmdLookback,
990
+ cmdPromote: () => cmdPromote,
991
+ cmdRecategorize: () => cmdRecategorize,
992
+ cmdReindex: () => cmdReindex,
993
+ cmdResearch: () => cmdResearch,
994
+ cmdSetDue: () => cmdSetDue,
995
+ cmdSetPriority: () => cmdSetPriority,
996
+ cmdSetProject: () => cmdSetProject,
997
+ cmdSetStatus: () => cmdSetStatus,
998
+ cmdShow: () => cmdShow,
999
+ domainGroups: () => domainGroups,
1000
+ inferProjectFromText: () => inferProjectFromText,
1001
+ projectDomainMap: () => projectDomainMap,
1002
+ projectIdFromPath: () => projectIdFromPath,
1003
+ resolveProjectId: () => resolveProjectId,
1004
+ today: () => today
1005
+ });
1006
+ import { join as join5 } from "path";
1007
+ import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
1008
+ var today = () => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1009
+ function cmdList(store, opts) {
1010
+ let rows = store.all();
1011
+ if (opts.type) rows = rows.filter((d) => d.type === opts.type);
1012
+ if (opts.status) rows = rows.filter((d) => d.object.status === opts.status);
1013
+ if (opts.project) {
1014
+ const want = resolveProjectId(store, opts.project);
1015
+ if (!want) return `(unknown project filter: ${opts.project})`;
1016
+ rows = rows.filter((d) => d.object.project === want);
1017
+ }
1018
+ if (rows.length === 0) return "(no matching objects)";
1019
+ return rows.sort((a, b) => a.type.localeCompare(b.type) || a.id.localeCompare(b.id)).map((d) => {
1020
+ const o = d.object;
1021
+ const proj = o.project ? o.project.replace(/^projects\//, "") : null;
1022
+ const extras = [o.priority, o.due ? `due:${o.due}` : null, proj ? `proj:${proj}` : null].filter(Boolean).join(" ");
1023
+ return `${pad(d.type, 12)} ${pad(o.status ?? "-", 10)} ${pad(extras, 18)} ${d.id}${o.title ? ` \u2014 ${o.title}` : ""}`;
1024
+ }).join("\n");
1025
+ }
1026
+ function cmdDaily(store, idx, date, carryOver) {
1027
+ const board = idx.dailyBoard(date);
1028
+ const lines = [`# Daily \u2014 ${date}`];
1029
+ if (!board.daily_id) lines.push(`(no daily plan file at daily/${date}; run /planning-daily or 'kp capture')`);
1030
+ if (board.body.trim()) lines.push("", board.body.trim());
1031
+ if (board.overdue.length) {
1032
+ lines.push("", "## \u26A0 Overdue");
1033
+ for (const r of board.overdue) lines.push(` - ${r.title ?? r.id} (due ${r.due}${r.priority ? `, ${r.priority}` : ""}) (${r.id})`);
1034
+ }
1035
+ if (board.due_today.length) {
1036
+ lines.push("", "## Due today (must-have)");
1037
+ for (const r of board.due_today) lines.push(` - ${r.title ?? r.id}${r.priority ? ` [${r.priority}]` : ""} (${r.id})`);
1038
+ }
1039
+ lines.push("", "## Tasks by lane");
1040
+ for (const [lane, rows] of Object.entries(board.lanes)) {
1041
+ if (rows.length === 0) continue;
1042
+ lines.push(` ${lane}:`);
1043
+ for (const r of rows) lines.push(` - [${r.status === "done" ? "x" : " "}] ${r.title ?? r.id} (${r.id})`);
1044
+ }
1045
+ if (carryOver) {
1046
+ const prev = previousDay(date);
1047
+ const prevBoard = idx.dailyBoard(prev);
1048
+ const unfinished = [...prevBoard.lanes.today, ...prevBoard.lanes.in_progress, ...prevBoard.lanes.inbox];
1049
+ if (unfinished.length) {
1050
+ lines.push("", `## Carry-over from ${prev}`);
1051
+ for (const r of unfinished) lines.push(` - ${r.title ?? r.id} (${r.id})`);
1052
+ }
1053
+ }
1054
+ return lines.join("\n");
1055
+ }
1056
+ function cmdBlocked(idx, kbRoot) {
1057
+ const blocked = idx.blockedByKnowledge(kbRoot).filter((b) => b.status !== "resolved");
1058
+ if (blocked.length === 0) return "(nothing blocked by knowledge)";
1059
+ return blocked.map((b) => {
1060
+ const flag = !b.exists ? "MISSING" : b.resolvable ? "RESOLVABLE (decision recorded)" : "open";
1061
+ return `${flag.padEnd(30)} ${b.id}
1062
+ \u2192 ${b.path}${b.reason ? `
1063
+ reason: ${b.reason}` : ""}`;
1064
+ }).join("\n");
1065
+ }
1066
+ function cmdGraph(store, kbRoot, json) {
1067
+ const g = buildGraph(store, kbRoot);
1068
+ if (json) return JSON.stringify(g, null, 2);
1069
+ return `graph: ${g.nodes.length} nodes, ${g.edges.length} edges
1070
+ by type: ${countBy(g.nodes.map((n) => n.type))}`;
1071
+ }
1072
+ function cmdReindex(store, root) {
1073
+ const res = persistIndex(store, join5(root, ".index/planning.db"));
1074
+ const warnings = store.getWarnings();
1075
+ return `reindexed \u2192 ${join5(root, ".index/planning.db")}
1076
+ ${res.objects} objects, ${res.edges} edges${warnings.length ? `
1077
+ ${warnings.length} warning(s):
1078
+ ${warnings.join("\n ")}` : ""}`;
1079
+ }
1080
+ function cmdCapture(store, text, domain) {
1081
+ const id = uniqueId(store, `ideas/${slugify(text)}`);
1082
+ store.write({ id, type: "idea", title: text, status: "capture", domain, created: today() });
1083
+ return { id };
1084
+ }
1085
+ function cmdPromote(store, ideaId, toId) {
1086
+ const idea = store.get(ideaId);
1087
+ if (!idea || idea.type !== "idea") throw new Error(`not an idea: ${ideaId}`);
1088
+ const o = idea.object;
1089
+ const planId = toId ?? uniqueId(store, `plans/${ideaId.split("/").pop()}`);
1090
+ const isLife = o.domain && o.domain !== "tech";
1091
+ store.write(
1092
+ {
1093
+ id: planId,
1094
+ type: "plan",
1095
+ title: o.title ?? planId,
1096
+ status: "plan",
1097
+ kind: isLife ? "life" : "dev",
1098
+ domain: o.domain,
1099
+ project: o.project,
1100
+ promoted_from: ideaId,
1101
+ blocked_by: o.blocked_by ?? [],
1102
+ cites: o.cites ?? [],
1103
+ created: today()
1104
+ },
1105
+ `# ${o.title ?? planId}
1106
+
1107
+ ## Goal
1108
+
1109
+ _(promoted from [[${ideaId}]])_
1110
+
1111
+ ## Steps
1112
+
1113
+ - [ ] \u2026
1114
+ `
1115
+ );
1116
+ store.patch(ideaId, (d) => {
1117
+ d.status = "done";
1118
+ d.promoted_to = planId;
1119
+ });
1120
+ store.appendEdge({ kind: "promoted_to", from: ideaId, to: planId, created_at: (/* @__PURE__ */ new Date()).toISOString() });
1121
+ return { ideaId, planId };
1122
+ }
1123
+ function cmdLinkSession(store, sessionsRoot, objectId, uuid) {
1124
+ const obj = store.get(objectId);
1125
+ if (!obj) throw new Error(`object not found: ${objectId}`);
1126
+ store.patch(objectId, (d) => {
1127
+ const refs = new Set(d.linked_sessions ?? []);
1128
+ refs.add(uuid);
1129
+ d.linked_sessions = [...refs];
1130
+ });
1131
+ store.appendEdge({ kind: "evidence", from: `session:${uuid}`, to: objectId, created_at: (/* @__PURE__ */ new Date()).toISOString() });
1132
+ const envelopePath = findSessionEnvelope(sessionsRoot, uuid);
1133
+ let envelopeUpdated = false;
1134
+ if (envelopePath) envelopeUpdated = addPlanningRef(envelopePath, objectId);
1135
+ return { objectId, uuid, envelopeUpdated, envelopePath };
1136
+ }
1137
+ function cmdInsightIngest(store, text, source, sourceRef) {
1138
+ const bullets = extractBullets(text).filter((b) => b.text.length > 3);
1139
+ const date = today();
1140
+ const id = uniqueId(store, `insights/${date}-${slugify(source)}`);
1141
+ const suggested = bullets.map((b) => b.text);
1142
+ store.write(
1143
+ {
1144
+ id,
1145
+ type: "insight",
1146
+ title: `${source} \u2014 ${date}`,
1147
+ source,
1148
+ source_ref: sourceRef,
1149
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
1150
+ status: "new"
1151
+ },
1152
+ `# ${source} \u2014 ${date}
1153
+
1154
+ ## Suggested
1155
+
1156
+ ${suggested.map((s) => `- [ ] ${s}`).join("\n")}
1157
+ `
1158
+ );
1159
+ return { id, suggested: suggested.length };
1160
+ }
1161
+ function cmdShow(store, kbRoot, id) {
1162
+ const doc = store.get(id);
1163
+ if (!doc) throw new Error(`object not found: ${id}`);
1164
+ const o = doc.object;
1165
+ const all = store.all();
1166
+ const ref = (oid) => {
1167
+ const d = store.get(oid);
1168
+ return d ? { id: d.id, type: d.type, title: d.object.title ?? d.id, status: d.object.status } : { id: oid, missing: true };
1169
+ };
1170
+ const cites = (o.cites ?? []).map((p) => {
1171
+ const abs = resolveKnowledgeRef(p, kbRoot);
1172
+ return { path: p, exists: existsSync5(abs), title: existsSync5(abs) ? readTitle(abs) : void 0 };
1173
+ });
1174
+ const blocked_by = (doc.object.blocked_by ?? []).map((r) => inspectKnowledgeRef(id, r, kbRoot));
1175
+ const children = all.filter((d) => {
1176
+ const c = d.object;
1177
+ return d.id !== id && (c.project === id || c.plan === id || c.daily === id);
1178
+ }).map((d) => ref(d.id));
1179
+ const parentId = o.plan || o.project || void 0;
1180
+ return {
1181
+ id: doc.id,
1182
+ type: doc.type,
1183
+ relpath: doc.relpath,
1184
+ path: doc.path,
1185
+ frontmatter: o,
1186
+ title: o.title ?? doc.id,
1187
+ status: o.status,
1188
+ domain: o.domain,
1189
+ body: doc.body,
1190
+ cites,
1191
+ blocked_by,
1192
+ depends_on: (o.depends_on ?? []).map(ref),
1193
+ related: (o.related ?? []).map(ref),
1194
+ linked_sessions: o.linked_sessions ?? [],
1195
+ parent: parentId ? ref(parentId) : null,
1196
+ children,
1197
+ promoted_to: o.promoted_to ? ref(o.promoted_to) : null,
1198
+ promoted_from: o.promoted_from ? ref(o.promoted_from) : null
1199
+ };
1200
+ }
1201
+ function cmdExport(store, idx, config, date) {
1202
+ return {
1203
+ generated_at: (/* @__PURE__ */ new Date()).toISOString(),
1204
+ root: store.root,
1205
+ kb_root: config.kb_root,
1206
+ sessions_root: config.sessions_root,
1207
+ counts: idx.counts(),
1208
+ objects: idx.all(),
1209
+ inbox: idx.listByStatus("capture"),
1210
+ board: idx.dailyBoard(date),
1211
+ blocked: idx.blockedByKnowledge(config.kb_root).filter((b) => b.status !== "resolved"),
1212
+ graph: buildGraph(store, config.kb_root)
1213
+ };
1214
+ }
1215
+ function cmdCalendar(idx, from, to) {
1216
+ const rows = idx.calendar(from, to);
1217
+ if (rows.length === 0) return `(no tasks due between ${from} and ${to})`;
1218
+ const lines = [`# Calendar ${from} \u2192 ${to}`];
1219
+ let day = "";
1220
+ const now = today();
1221
+ for (const r of rows) {
1222
+ if (r.due !== day) {
1223
+ day = r.due;
1224
+ const marker = day < now ? " \u26A0 overdue" : day === now ? " \u2190 today" : "";
1225
+ lines.push("", `## ${day}${marker}`);
1226
+ }
1227
+ const doneMark = r.status === "done" ? "x" : r.status === "outdated" ? "~" : " ";
1228
+ lines.push(` - [${doneMark}] ${r.title ?? r.id}${r.priority ? ` [${r.priority}]` : ""} (${r.status}) (${r.id})`);
1229
+ }
1230
+ return lines.join("\n");
1231
+ }
1232
+ function cmdSetDue(store, id, due) {
1233
+ if (!store.get(id)) throw new Error(`object not found: ${id}`);
1234
+ if (due !== "-" && !/^\d{4}-\d{2}-\d{2}$/.test(due)) throw new Error(`due must be YYYY-MM-DD or '-' to clear: ${due}`);
1235
+ store.patch(id, (d) => {
1236
+ if (due === "-") delete d.due;
1237
+ else d.due = due;
1238
+ d.updated = today();
1239
+ });
1240
+ }
1241
+ function cmdSetPriority(store, id, priority) {
1242
+ if (!store.get(id)) throw new Error(`object not found: ${id}`);
1243
+ if (priority !== "-" && !/^p[0-3]$/.test(priority)) throw new Error(`priority must be p0..p3 or '-' to clear: ${priority}`);
1244
+ store.patch(id, (d) => {
1245
+ if (priority === "-") delete d.priority;
1246
+ else d.priority = priority;
1247
+ d.updated = today();
1248
+ });
1249
+ }
1250
+ function cmdSetStatus(store, id, status, note) {
1251
+ if (!store.get(id)) throw new Error(`object not found: ${id}`);
1252
+ store.patch(id, (d) => {
1253
+ d.status = status;
1254
+ d.updated = today();
1255
+ });
1256
+ const text = note?.trim();
1257
+ if (text) {
1258
+ const doc = store.get(id);
1259
+ const base = (doc.body ?? "").replace(/\s+$/, "");
1260
+ const body = `${base ? base + "\n\n" : ""}## Resolution (${status} \u2014 ${today()})
1261
+
1262
+ ${text}
1263
+ `;
1264
+ store.write(doc.data, body);
1265
+ }
1266
+ }
1267
+ var CLOSING_STATUSES = ["done", "deferred", "outdated", "parked", "archived"];
1268
+ function resolveProjectId(store, ref) {
1269
+ if (ref === void 0 || ref === null || ref === "" || ref === "-") return null;
1270
+ const raw = ref.trim();
1271
+ if (!raw) return null;
1272
+ if (store.get(raw)?.type === "project") return raw;
1273
+ const asId = raw.startsWith("projects/") ? raw : `projects/${raw}`;
1274
+ if (store.get(asId)?.type === "project") return asId;
1275
+ const needle = raw.toLowerCase().replace(/^projects\//, "");
1276
+ for (const d of store.byType("project")) {
1277
+ const o = d.object;
1278
+ const slug = (o.catalog_slug ?? d.id.replace(/^projects\//, "")).toLowerCase();
1279
+ if (slug === needle || d.id.replace(/^projects\//, "").toLowerCase() === needle) return d.id;
1280
+ const aliases = (o.aliases ?? []).map((a) => String(a).toLowerCase());
1281
+ if (aliases.includes(needle) || aliases.includes(raw.toLowerCase())) return d.id;
1282
+ if ((o.title ?? "").toLowerCase() === needle) return d.id;
1283
+ }
1284
+ throw new Error(`unknown project: ${ref} (use projects/<slug>, catalog slug, or alias like CS/CSV/CB/KP)`);
1285
+ }
1286
+ function cmdSetProject(store, id, project) {
1287
+ if (!store.get(id)) throw new Error(`object not found: ${id}`);
1288
+ const resolved = project === "-" || project === "" ? null : resolveProjectId(store, project);
1289
+ store.patch(id, (d) => {
1290
+ if (!resolved) delete d.project;
1291
+ else d.project = resolved;
1292
+ d.updated = today();
1293
+ });
1294
+ }
1295
+ function cmdEdit(store, id, edits) {
1296
+ const existing = store.get(id);
1297
+ if (!existing) throw new Error(`object not found: ${id}`);
1298
+ const data = { ...existing.data };
1299
+ if (edits.title !== void 0) data.title = edits.title;
1300
+ if (edits.status !== void 0) data.status = edits.status;
1301
+ if (edits.domain !== void 0) data.domain = edits.domain;
1302
+ if (edits.lane !== void 0) data.lane = edits.lane;
1303
+ if (edits.project !== void 0) {
1304
+ const resolved = resolveProjectId(store, edits.project);
1305
+ if (!resolved) delete data.project;
1306
+ else data.project = resolved;
1307
+ }
1308
+ data.updated = today();
1309
+ store.write(data, edits.body ?? existing.body);
1310
+ }
1311
+ var TYPE_DEFAULT_STATUS = { idea: "capture", task: "inbox", plan: "plan", project: "active", thought: "new" };
1312
+ function cmdRecategorize(store, id, opts) {
1313
+ const existing = store.get(id);
1314
+ if (!existing) throw new Error(`object not found: ${id}`);
1315
+ if (!opts.toType || opts.toType === existing.type) {
1316
+ store.patch(id, (d) => {
1317
+ if (opts.domain !== void 0) d.domain = opts.domain;
1318
+ if (opts.lane !== void 0) d.lane = opts.lane;
1319
+ d.updated = today();
1320
+ });
1321
+ return { newId: id };
1322
+ }
1323
+ const slug = id.split("/").slice(1).join("/");
1324
+ const newId = `${opts.toType}s/${slug}`;
1325
+ const data = { ...existing.data };
1326
+ delete data.schema;
1327
+ data.id = newId;
1328
+ data.type = opts.toType;
1329
+ data.status = TYPE_DEFAULT_STATUS[opts.toType] ?? "inbox";
1330
+ data.recategorized_from = id;
1331
+ if (opts.domain !== void 0) data.domain = opts.domain;
1332
+ if (opts.lane !== void 0) data.lane = opts.lane;
1333
+ data.updated = today();
1334
+ store.write(data, existing.body);
1335
+ store.delete(id);
1336
+ rewriteRefs(store, id, newId);
1337
+ return { newId };
1338
+ }
1339
+ function rewriteRefs(store, fromId, toId) {
1340
+ for (const doc of store.all()) {
1341
+ let changed = false;
1342
+ const d = { ...doc.data };
1343
+ if (d.parent === fromId) {
1344
+ d.parent = toId;
1345
+ changed = true;
1346
+ }
1347
+ for (const key of ["cites", "blocked_by"]) {
1348
+ const arr = d[key];
1349
+ if (Array.isArray(arr)) {
1350
+ const next = arr.map((r) => typeof r === "string" && r === fromId ? toId : r);
1351
+ if (JSON.stringify(next) !== JSON.stringify(arr)) {
1352
+ d[key] = next;
1353
+ changed = true;
1354
+ }
1355
+ }
1356
+ }
1357
+ if (changed) store.write(d, doc.body);
1358
+ }
1359
+ for (const e of store.fileEdges()) {
1360
+ if (e.from === fromId || e.to === fromId) {
1361
+ store.appendEdge({ ...e, from: e.from === fromId ? toId : e.from, to: e.to === fromId ? toId : e.to });
1362
+ }
1363
+ }
1364
+ }
1365
+ function cmdDelete(store, id) {
1366
+ if (!store.get(id)) throw new Error(`object not found: ${id}`);
1367
+ return store.delete(id);
1368
+ }
1369
+ function cmdResearch(store, kbRoot, id) {
1370
+ const doc = store.get(id);
1371
+ if (!doc) throw new Error(`object not found: ${id}`);
1372
+ const o = doc.object;
1373
+ const refs = [
1374
+ ...o.cites ?? [],
1375
+ ...o.kb_paths ?? []
1376
+ ];
1377
+ return [
1378
+ `Research this planning item against the knowledge base, then connect it.`,
1379
+ ``,
1380
+ `Item: ${o.title} (${id}, type ${doc.type}, domain ${o.domain ?? "\u2014"})`,
1381
+ doc.body.trim() ? `
1382
+ ${doc.body.trim()}
1383
+ ` : "",
1384
+ `Knowledge base root: ${kbRoot}`,
1385
+ refs.length ? `Already-linked knowledge: ${refs.join(", ")}` : "",
1386
+ ``,
1387
+ `Run the **planning-research** skill: search the KB for related docs and prior art,`,
1388
+ `add cites to the relevant ones, append a research summary to this item, flag any`,
1389
+ `blocked_by open questions, suggest a refinement (or promote-to-task), and finally`,
1390
+ `link this session to the item: \`kp link-session ${id} <this-session-uuid>\`.`
1391
+ ].filter((l) => l !== "").join("\n");
1392
+ }
1393
+ function cmdIngestGdoc(store, text, opts = {}) {
1394
+ const minLen = opts.minLen ?? 12;
1395
+ const max = opts.max ?? 25;
1396
+ const source = opts.source ?? "gdoc";
1397
+ if (opts.prevText == null) {
1398
+ return { created: [], skipped: 0, newBullets: 0, baselined: true, capped: 0 };
1399
+ }
1400
+ const prevSet = new Set(extractBullets(opts.prevText).map((b) => b.text));
1401
+ const fresh = extractBullets(text).filter((b) => b.text.length >= minLen && b.depth <= 2 && !prevSet.has(b.text));
1402
+ const CLUSTER_MIN = 5;
1403
+ const isTodoCtx = (b) => /\btodo\b/i.test(b.context ?? "");
1404
+ const taskish = fresh.filter((b) => b.checked !== void 0 || isTodoCtx(b));
1405
+ const plain = fresh.filter((b) => b.checked === void 0 && !isTodoCtx(b));
1406
+ const byCtx = /* @__PURE__ */ new Map();
1407
+ for (const b of plain) {
1408
+ const k = b.context ?? "";
1409
+ if (!byCtx.has(k)) byCtx.set(k, []);
1410
+ byCtx.get(k).push(b);
1411
+ }
1412
+ const loose = [];
1413
+ const clusters = [];
1414
+ for (const [ctx, bs] of byCtx) {
1415
+ if (ctx && bs.length >= CLUSTER_MIN) clusters.push({ context: ctx, bullets: bs });
1416
+ else loose.push(...bs);
1417
+ }
1418
+ const seedInput = [...taskish.map((b) => ({ ...b, checked: b.checked ?? false })), ...loose];
1419
+ const seeds = toSeeds(seedInput.slice(0, max), { domain: opts.domain, source });
1420
+ const created = [];
1421
+ let skipped = 0;
1422
+ for (const seed of seeds) {
1423
+ if (store.get(seed.id)) {
1424
+ skipped++;
1425
+ continue;
1426
+ }
1427
+ store.write(
1428
+ {
1429
+ id: seed.id,
1430
+ type: seed.type,
1431
+ title: seed.title,
1432
+ status: seed.status,
1433
+ domain: seed.domain,
1434
+ source: seed.source,
1435
+ source_url: opts.sourceUrl,
1436
+ context: seed.context,
1437
+ surfaced_on: today(),
1438
+ created: today()
1439
+ },
1440
+ seed.title
1441
+ );
1442
+ created.push(seed.id);
1443
+ }
1444
+ const clips = [];
1445
+ for (const c of clusters) {
1446
+ const body = c.bullets.map((b) => `- ${b.text}`).join("\n");
1447
+ const bodyKey = body.replace(/\s+/g, " ").trim();
1448
+ const dup = store.all().some((d) => d.type === "thought" && d.body.replace(/\s+/g, " ").trim() === bodyKey);
1449
+ if (dup) {
1450
+ skipped++;
1451
+ continue;
1452
+ }
1453
+ const id = uniqueId(store, `thoughts/${slugify(c.context)}-notes`);
1454
+ store.write(
1455
+ {
1456
+ id,
1457
+ type: "thought",
1458
+ title: `${c.context} \u2014 pasted notes (${c.bullets.length})`,
1459
+ status: "new",
1460
+ kind: "clip",
1461
+ source,
1462
+ source_url: opts.sourceUrl,
1463
+ context: c.context,
1464
+ surfaced_on: today(),
1465
+ created: today()
1466
+ },
1467
+ body
1468
+ );
1469
+ created.push(id);
1470
+ clips.push({ context: c.context, count: c.bullets.length });
1471
+ }
1472
+ return {
1473
+ created,
1474
+ skipped,
1475
+ newBullets: fresh.length,
1476
+ baselined: false,
1477
+ capped: Math.max(0, seedInput.length - Math.min(seedInput.length, max)),
1478
+ clips
1479
+ };
1480
+ }
1481
+ function cmdIngestThoughts(store, text, opts = {}) {
1482
+ const minLen = opts.minLen ?? 60;
1483
+ const max = opts.max ?? 20;
1484
+ const source = opts.source ?? "gdoc";
1485
+ if (opts.prevText == null) {
1486
+ return { created: [], skipped: 0, newBlocks: 0, baselined: true, capped: 0 };
1487
+ }
1488
+ const prevSet = new Set(extractProse(opts.prevText, minLen).map((b) => b.text));
1489
+ const existingBodies = new Set(
1490
+ store.all().filter((d) => d.type === "thought").map((d) => d.body.replace(/\s+/g, " ").trim())
1491
+ );
1492
+ const fresh = extractProse(text, minLen).filter((b) => !prevSet.has(b.text) && !existingBodies.has(b.text));
1493
+ const take = fresh.slice(0, max);
1494
+ const created = [];
1495
+ let skipped = 0;
1496
+ for (const block of take) {
1497
+ const id = uniqueId(store, `thoughts/${slugify(block.text)}`);
1498
+ if (store.get(id)) {
1499
+ skipped++;
1500
+ continue;
1501
+ }
1502
+ const title = block.text.length > 90 ? block.text.slice(0, 87).trimEnd() + "\u2026" : block.text;
1503
+ store.write(
1504
+ {
1505
+ id,
1506
+ type: "thought",
1507
+ title,
1508
+ status: "new",
1509
+ source,
1510
+ source_url: opts.sourceUrl,
1511
+ context: block.context,
1512
+ surfaced_on: today(),
1513
+ created: today()
1514
+ },
1515
+ block.text
1516
+ );
1517
+ created.push(id);
1518
+ }
1519
+ return { created, skipped, newBlocks: fresh.length, baselined: false, capped: Math.max(0, fresh.length - take.length) };
1520
+ }
1521
+ function cmdDoctor(store, idx, config) {
1522
+ const counts = idx.counts();
1523
+ const blocked = idx.blockedByKnowledge(config.kb_root).filter((b) => b.status !== "resolved");
1524
+ const warnings = store.getWarnings();
1525
+ return [
1526
+ `store root: ${store.root}`,
1527
+ `kb_root: ${config.kb_root}`,
1528
+ `sessions: ${config.sessions_root}`,
1529
+ `objects: ${Object.entries(counts).map(([k, v]) => `${k}=${v}`).join(" ") || "(none)"}`,
1530
+ `fts5: ${idx.fts ? "yes" : "no (LIKE fallback)"}`,
1531
+ `blocked: ${blocked.length} (${blocked.filter((b) => b.resolvable).length} resolvable, ${blocked.filter((b) => !b.exists).length} missing)`,
1532
+ `warnings: ${warnings.length}${warnings.length ? "\n " + warnings.join("\n ") : ""}`
1533
+ ].join("\n");
1534
+ }
1535
+ function pad(s, n) {
1536
+ return (s ?? "").padEnd(n).slice(0, n);
1537
+ }
1538
+ function countBy(items) {
1539
+ const m = {};
1540
+ for (const i of items) m[i] = (m[i] ?? 0) + 1;
1541
+ return Object.entries(m).map(([k, v]) => `${k}=${v}`).join(" ");
1542
+ }
1543
+ function uniqueId(store, base) {
1544
+ if (!store.get(base)) return base;
1545
+ let n = 2;
1546
+ while (store.get(`${base}-${n}`)) n++;
1547
+ return `${base}-${n}`;
1548
+ }
1549
+ function previousDay(date) {
1550
+ const d = /* @__PURE__ */ new Date(date + "T00:00:00Z");
1551
+ d.setUTCDate(d.getUTCDate() - 1);
1552
+ return d.toISOString().slice(0, 10);
1553
+ }
1554
+ var DEFAULT_GROUPS = {
1555
+ work: ["career", "tech"],
1556
+ life: ["kids", "relationship", "finances", "relocation"]
1557
+ };
1558
+ function domainGroups(config) {
1559
+ const custom = config.groups;
1560
+ return custom && Object.keys(custom).length ? custom : DEFAULT_GROUPS;
1561
+ }
1562
+ function groupOfDomain(groups, domain) {
1563
+ if (!domain) return "other";
1564
+ for (const [g, domains] of Object.entries(groups)) if (domains.includes(domain)) return g;
1565
+ return "other";
1566
+ }
1567
+ function projectDomainMap(store) {
1568
+ const map = {};
1569
+ for (const d of store.byType("project")) {
1570
+ const o = d.object;
1571
+ const domain = o.domain ?? "tech";
1572
+ const base = d.id.split("/").pop();
1573
+ if (base) map[base] = domain;
1574
+ for (const repo of o.repos ?? []) {
1575
+ const name = repo.split("/").pop()?.replace(/\.git$/, "");
1576
+ if (name) map[name] = domain;
1577
+ }
1578
+ }
1579
+ return map;
1580
+ }
1581
+ function buildTopics(store, config) {
1582
+ const groups = domainGroups(config);
1583
+ const projDomain = projectDomainMap(store);
1584
+ const sessions = readAllSessions();
1585
+ const recentCutoff = (() => {
1586
+ const d = /* @__PURE__ */ new Date();
1587
+ d.setDate(d.getDate() - 30);
1588
+ return d.toISOString().slice(0, 10);
1589
+ })();
1590
+ const domains = {};
1591
+ const ensure = (domain) => {
1592
+ const g = groupOfDomain(groups, domain);
1593
+ return domains[domain] ??= { group: g, tasks: [], ideas: [], sessions: [] };
1594
+ };
1595
+ for (const d of store.byType("task")) {
1596
+ const o = d.object;
1597
+ if (o.status === "done" || o.status === "outdated") continue;
1598
+ ensure(o.domain ?? "other").tasks.push({ id: d.id, title: o.title, status: o.status, priority: o.priority, due: o.due });
1599
+ }
1600
+ for (const d of store.byType("idea")) {
1601
+ const o = d.object;
1602
+ if (o.status === "done" || o.status === "parked") continue;
1603
+ ensure(o.domain ?? "other").ideas.push({ id: d.id, title: o.title, status: o.status });
1604
+ }
1605
+ for (const s of sessions) {
1606
+ if (s.day < recentCutoff) continue;
1607
+ const domain = s.project_name && projDomain[s.project_name] || void 0;
1608
+ ensure(domain ?? "unmapped").sessions.push(s);
1609
+ }
1610
+ for (const d of Object.values(domains)) d.sessions = d.sessions.slice(-12).reverse();
1611
+ const grouped = {};
1612
+ for (const [domain, data] of Object.entries(domains)) {
1613
+ (grouped[data.group] ??= {})[domain] = data;
1614
+ }
1615
+ return { groups: grouped, group_config: groups };
1616
+ }
1617
+ function buildLookback(store, date) {
1618
+ const sessions = sessionsByDay(readAllSessions(), date, date)[date] ?? [];
1619
+ const worked = [];
1620
+ const created = [];
1621
+ const touched = [];
1622
+ const due = [];
1623
+ const NOISY_STATUS = /* @__PURE__ */ new Set(["parked", "archived", "outdated"]);
1624
+ for (const d of store.all()) {
1625
+ const o = d.object;
1626
+ const item = { id: d.id, type: d.type, title: o.title, status: o.status, priority: o.priority };
1627
+ const noisy = NOISY_STATUS.has(o.status ?? "");
1628
+ let hit = false;
1629
+ if (o.updated === date && !noisy && d.type !== "thought") {
1630
+ worked.push(item);
1631
+ hit = true;
1632
+ }
1633
+ if ((o.created === date || o.surfaced_on === date) && !noisy) {
1634
+ created.push(item);
1635
+ hit = true;
1636
+ }
1637
+ if (hit) touched.push(item);
1638
+ if (o.due === date) due.push({ ...item });
1639
+ }
1640
+ const daily = store.get(`daily/${date}`);
1641
+ return {
1642
+ date,
1643
+ sessions,
1644
+ tasks_worked: worked,
1645
+ tasks_created: created,
1646
+ tasks_touched: touched,
1647
+ tasks_due: due,
1648
+ daily_plan: daily ? { id: daily.id, body: daily.body ?? "" } : null
1649
+ };
1650
+ }
1651
+ function cmdLookback(store, date) {
1652
+ const lb = buildLookback(store, date);
1653
+ const lines = [`# Lookback \u2014 ${date}`];
1654
+ if (lb.daily_plan?.body?.trim()) lines.push("", "## Plan of the day", lb.daily_plan.body.trim().split("\n").slice(0, 12).join("\n"));
1655
+ lines.push("", `## Sessions (${lb.sessions.length})`);
1656
+ if (!lb.sessions.length) lines.push(" (none captured)");
1657
+ for (const s of lb.sessions) {
1658
+ const t = s.started_at ? new Date(s.started_at).toTimeString().slice(0, 5) : "--:--";
1659
+ lines.push(` ${t} [${s.project_name ?? "?"}] ${s.title ?? "(untitled)"}${s.minutes ? ` (${s.minutes}m)` : ""}`);
1660
+ }
1661
+ if (lb.tasks_due.length) {
1662
+ lines.push("", "## Was due");
1663
+ for (const t of lb.tasks_due) lines.push(` - ${t.title ?? t.id} (${t.status})`);
1664
+ }
1665
+ if (lb.tasks_worked.length) {
1666
+ lines.push("", "## Worked on (updated)");
1667
+ for (const t of lb.tasks_worked) lines.push(` - ${t.title ?? t.id} (${t.status})`);
1668
+ }
1669
+ return lines.join("\n");
1670
+ }
1671
+ var STOPWORDS = new Set("the a an and or of to in on for with from is are was were be this that it as at by i my we our you your do done did not no yes if then else new next now need needs use using used into over under fix fixed add added run running make let get got set see look review update updated create created check test tests please help want".split(" "));
1672
+ function titleKeywords(titles, top = 5) {
1673
+ const freq = /* @__PURE__ */ new Map();
1674
+ for (const t of titles) {
1675
+ for (const w of t.toLowerCase().split(/[^a-z0-9./-]+/)) {
1676
+ if (w.length < 3 || STOPWORDS.has(w)) continue;
1677
+ freq.set(w, (freq.get(w) ?? 0) + 1);
1678
+ }
1679
+ }
1680
+ return [...freq.entries()].filter(([, n]) => n > 1).sort((a, b) => b[1] - a[1]).slice(0, top).map(([w]) => w);
1681
+ }
1682
+ function cmdDeriveSessions(store, config) {
1683
+ const sessions = readAllSessions();
1684
+ const projDomain = projectDomainMap(store);
1685
+ const groups = domainGroups(config);
1686
+ const byProject = /* @__PURE__ */ new Map();
1687
+ for (const s of sessions) {
1688
+ const key = s.project_name ?? "(unknown)";
1689
+ (byProject.get(key) ?? byProject.set(key, []).get(key)).push(s);
1690
+ }
1691
+ const date = today();
1692
+ const id = `insights/${date}-sessions-derived`;
1693
+ const lines = [
1694
+ `# Sessions-derived topics \u2014 ${date}`,
1695
+ "",
1696
+ `Re-analysis of ${sessions.length} captured sessions (git store + native Claude transcripts),`,
1697
+ "clustered by project with per-cluster keywords and samples. Suggestions are advisory \u2014",
1698
+ "promote to tasks/ideas with `kp capture` / the dashboard, nothing was auto-created.",
1699
+ ""
1700
+ ];
1701
+ const clusters = [...byProject.entries()].sort((a, b) => b[1].length - a[1].length);
1702
+ for (const [project, list] of clusters) {
1703
+ const days = list.map((s) => s.day).sort();
1704
+ const domain = projDomain[project];
1705
+ const group = groupOfDomain(groups, domain);
1706
+ const titles = list.map((s) => s.title ?? "").filter(Boolean);
1707
+ const kws = titleKeywords(titles);
1708
+ lines.push(`## ${project} \u2014 ${list.length} session(s), ${days[0]} \u2192 ${days[days.length - 1]}`);
1709
+ lines.push(`- group/domain: ${group}${domain ? ` / ${domain}` : " / (unmapped \u2014 consider a planning project entry)"}`);
1710
+ if (kws.length) lines.push(`- recurring themes: ${kws.join(", ")}`);
1711
+ for (const s of list.filter((x) => x.title).slice(-4)) lines.push(`- ${s.day}: ${s.title}`);
1712
+ if (kws.length) lines.push(`- [ ] suggested: track "${project}: ${kws.slice(0, 3).join(" / ")}" as a task or idea if still active`);
1713
+ lines.push("");
1714
+ }
1715
+ store.write(
1716
+ {
1717
+ id,
1718
+ type: "insight",
1719
+ title: `Sessions-derived topics \u2014 ${date}`,
1720
+ source: "sessions-derive",
1721
+ generated_at: date,
1722
+ confidence: "medium",
1723
+ status: "new"
1724
+ },
1725
+ lines.join("\n")
1726
+ );
1727
+ return { id, clusters: clusters.length, sessions: sessions.length };
1728
+ }
1729
+ function cmdEditObject(store, id, edits) {
1730
+ const existing = store.get(id);
1731
+ if (!existing) throw new Error(`object not found: ${id}`);
1732
+ const data = { ...existing.data };
1733
+ if (edits.title !== void 0 && edits.title.trim()) data.title = edits.title.trim();
1734
+ data.updated = today();
1735
+ store.write(
1736
+ data,
1737
+ edits.body !== void 0 ? edits.body : existing.body
1738
+ );
1739
+ }
1740
+ function cmdCreate(store, input) {
1741
+ const title = input.title.trim();
1742
+ if (!title) throw new Error("create needs a title");
1743
+ const type = input.type || "task";
1744
+ const folder = type === "idea" ? "ideas" : type === "plan" ? "plans" : type === "thought" ? "thoughts" : "tasks";
1745
+ const id = uniqueId(store, `${folder}/${slugify(title)}`);
1746
+ const defaultStatus = TYPE_DEFAULT_STATUS[type] ?? "inbox";
1747
+ const data = {
1748
+ id,
1749
+ type,
1750
+ title,
1751
+ status: input.status || defaultStatus,
1752
+ created: today()
1753
+ };
1754
+ if (input.domain) data.domain = input.domain;
1755
+ if (input.project) {
1756
+ const resolved = resolveProjectId(store, input.project);
1757
+ if (resolved) data.project = resolved;
1758
+ }
1759
+ if (input.priority && /^p[0-3]$/.test(input.priority)) data.priority = input.priority;
1760
+ if (input.due && /^\d{4}-\d{2}-\d{2}$/.test(input.due)) data.due = input.due;
1761
+ store.write(data, input.body);
1762
+ return { id };
1763
+ }
1764
+ function inferProjectFromText(store, id, text) {
1765
+ const cfg = store.config();
1766
+ const compile = (s) => {
1767
+ try {
1768
+ return new RegExp(s, "i");
1769
+ } catch {
1770
+ return null;
1771
+ }
1772
+ };
1773
+ for (const r of cfg.project_id_rules ?? []) {
1774
+ const re = compile(r.pattern);
1775
+ if (re && re.test(id) && store.get(r.project)?.type === "project") return r.project;
1776
+ }
1777
+ const hay = `${id}
1778
+ ${text}`;
1779
+ for (const rule of cfg.project_rules ?? []) {
1780
+ if (store.get(rule.project)?.type !== "project") continue;
1781
+ if ((rule.patterns ?? []).some((p) => {
1782
+ const re = compile(p);
1783
+ return !!re && re.test(hay);
1784
+ })) return rule.project;
1785
+ }
1786
+ return null;
1787
+ }
1788
+ function cmdDeriveProjects(store, opts = {}) {
1789
+ const types = new Set(opts.types ?? ["task", "idea", "thought", "plan"]);
1790
+ const force = !!opts.force;
1791
+ const dryRun = !!opts.dryRun;
1792
+ const result = {
1793
+ examined: 0,
1794
+ labeled: 0,
1795
+ skippedHasProject: 0,
1796
+ skippedNoMatch: 0,
1797
+ changes: []
1798
+ };
1799
+ for (const d of store.all()) {
1800
+ if (!types.has(d.type)) continue;
1801
+ result.examined++;
1802
+ const o = d.object;
1803
+ if (o.project && !force) {
1804
+ result.skippedHasProject++;
1805
+ continue;
1806
+ }
1807
+ const o2 = d.object;
1808
+ const hay = [
1809
+ d.id,
1810
+ o2.title ?? "",
1811
+ o2.domain ?? "",
1812
+ o2.context ?? "",
1813
+ o2.source ?? "",
1814
+ ...o2.related ?? [],
1815
+ ...o2.cites ?? [],
1816
+ d.body ?? ""
1817
+ ].join("\n");
1818
+ const inferred = inferProjectFromText(store, d.id, hay);
1819
+ if (!inferred) {
1820
+ if (force && o.project) {
1821
+ result.changes.push({ id: d.id, project: "(cleared)", previous: o.project });
1822
+ if (!dryRun) {
1823
+ store.patch(d.id, (data) => {
1824
+ delete data.project;
1825
+ data.updated = today();
1826
+ });
1827
+ }
1828
+ result.labeled++;
1829
+ } else {
1830
+ result.skippedNoMatch++;
1831
+ }
1832
+ continue;
1833
+ }
1834
+ if (o.project === inferred) {
1835
+ result.skippedHasProject++;
1836
+ continue;
1837
+ }
1838
+ result.changes.push({ id: d.id, project: inferred, previous: o.project });
1839
+ if (!dryRun) {
1840
+ store.patch(d.id, (data) => {
1841
+ data.project = inferred;
1842
+ data.updated = today();
1843
+ });
1844
+ }
1845
+ result.labeled++;
1846
+ }
1847
+ return result;
1848
+ }
1849
+ function projectIdFromPath(store, projectPath) {
1850
+ if (!projectPath) return null;
1851
+ const p = projectPath.replace(/\/+$/, "");
1852
+ let best = null;
1853
+ for (const d of store.byType("project")) {
1854
+ const o = d.object;
1855
+ for (const repo of o.repos ?? []) {
1856
+ const m = repo.match(/[:/]([^/]+?)(?:\.git)?$/);
1857
+ const name = m?.[1];
1858
+ if (!name) continue;
1859
+ if (p.includes(`/unpolarize/${name}`) || p.endsWith(`/${name}`) || p.includes(`/projects/${name}`)) {
1860
+ const len = name.length;
1861
+ if (!best || len > best.len) best = { id: d.id, len };
1862
+ }
1863
+ }
1864
+ const slug = o.catalog_slug ?? d.id.replace(/^projects\//, "");
1865
+ if (p.includes(slug) && slug.length > 2) {
1866
+ const len = slug.length;
1867
+ if (!best || len > best.len) best = { id: d.id, len };
1868
+ }
1869
+ }
1870
+ for (const rule of store.config().project_path_rules ?? []) {
1871
+ let re = null;
1872
+ try {
1873
+ re = new RegExp(rule.pattern, "i");
1874
+ } catch {
1875
+ re = null;
1876
+ }
1877
+ if (re && re.test(p) && store.get(rule.project)?.type === "project") {
1878
+ if (!best || rule.project.length > best.len) best = { id: rule.project, len: rule.project.length };
1879
+ }
1880
+ }
1881
+ return best?.id ?? null;
1882
+ }
1883
+ function cmdLabelSessions(store, opts = {}) {
1884
+ const root = opts.sessionsRoot ?? store.config().sessions_root;
1885
+ const hosts = join5(root, "hosts");
1886
+ const result = { examined: 0, labeled: 0, skipped: 0, changes: [] };
1887
+ if (!existsSync5(hosts)) return result;
1888
+ const walk = (dir) => {
1889
+ const out = [];
1890
+ for (const name of readdirSync4(dir)) {
1891
+ const p = join5(dir, name);
1892
+ const st = statSync2(p);
1893
+ if (st.isDirectory()) out.push(...walk(p));
1894
+ else if (name === "session.json") out.push(p);
1895
+ }
1896
+ return out;
1897
+ };
1898
+ for (const path2 of walk(hosts)) {
1899
+ result.examined++;
1900
+ let env;
1901
+ try {
1902
+ env = JSON.parse(readFileSync5(path2, "utf8"));
1903
+ } catch {
1904
+ result.skipped++;
1905
+ continue;
1906
+ }
1907
+ const projectId = projectIdFromPath(store, env.project_path);
1908
+ if (!projectId) {
1909
+ result.skipped++;
1910
+ continue;
1911
+ }
1912
+ const proj = store.get(projectId)?.object;
1913
+ const slug = proj?.catalog_slug ?? projectId.replace(/^projects\//, "");
1914
+ const label = `project:${slug}`;
1915
+ const labels = [...env.labels ?? []];
1916
+ const has = labels.some((l) => l === label || l.startsWith("project:"));
1917
+ if (has && !opts.force) {
1918
+ const existing = labels.find((l) => l.startsWith("project:"));
1919
+ if (existing === label) {
1920
+ result.skipped++;
1921
+ continue;
1922
+ }
1923
+ if (!opts.force && existing) {
1924
+ result.skipped++;
1925
+ continue;
1926
+ }
1927
+ }
1928
+ const next = labels.filter((l) => !l.startsWith("project:"));
1929
+ next.push(label);
1930
+ result.changes.push({ path: path2, uuid: env.session_id ?? "", project: projectId, label });
1931
+ if (!opts.dryRun) {
1932
+ env.labels = next;
1933
+ writeFileSync3(path2, JSON.stringify(env, null, 2) + "\n", "utf8");
1934
+ }
1935
+ result.labeled++;
1936
+ }
1937
+ return result;
1938
+ }
1939
+
1940
+ export {
1941
+ SCHEMA_VERSION,
1942
+ ns,
1943
+ OBJECT_TYPES,
1944
+ IDEA_STATUS,
1945
+ PLAN_STATUS,
1946
+ TASK_STATUS,
1947
+ SESSION_PHASE,
1948
+ INSIGHT_STATUS,
1949
+ THOUGHT_STATUS,
1950
+ KnowledgeRefSchema,
1951
+ DomainSchema,
1952
+ ProjectSchema,
1953
+ CatalogEntrySchema,
1954
+ IdeaSchema,
1955
+ PlanSchema,
1956
+ TaskSchema,
1957
+ DailyPlanSchema,
1958
+ ReflectionSchema,
1959
+ InsightSchema,
1960
+ ThoughtSchema,
1961
+ SCHEMA_BY_TYPE,
1962
+ ObjectSchema,
1963
+ EDGE_KINDS,
1964
+ EdgeSchema,
1965
+ parseObject,
1966
+ safeParseObject,
1967
+ parseMarkdown,
1968
+ serializeMarkdown,
1969
+ DEFAULT_CONFIG,
1970
+ Store,
1971
+ walkMarkdown,
1972
+ resolveKnowledgeRef,
1973
+ readTitle,
1974
+ hasDecision,
1975
+ inspectKnowledgeRef,
1976
+ PlanningIndex,
1977
+ persistIndex,
1978
+ buildGraph,
1979
+ extractBullets,
1980
+ extractProse,
1981
+ slugify,
1982
+ toSeeds,
1983
+ findSessionEnvelope,
1984
+ addPlanningRef,
1985
+ readAllSessions,
1986
+ sessionsByDay,
1987
+ today,
1988
+ cmdList,
1989
+ cmdDaily,
1990
+ cmdBlocked,
1991
+ cmdGraph,
1992
+ cmdReindex,
1993
+ cmdCapture,
1994
+ cmdPromote,
1995
+ cmdLinkSession,
1996
+ cmdInsightIngest,
1997
+ cmdShow,
1998
+ cmdExport,
1999
+ cmdCalendar,
2000
+ cmdSetDue,
2001
+ cmdSetPriority,
2002
+ cmdSetStatus,
2003
+ cmdSetProject,
2004
+ cmdEdit,
2005
+ cmdRecategorize,
2006
+ cmdDelete,
2007
+ cmdResearch,
2008
+ cmdIngestGdoc,
2009
+ cmdIngestThoughts,
2010
+ cmdDoctor,
2011
+ buildTopics,
2012
+ buildLookback,
2013
+ cmdLookback,
2014
+ cmdDeriveSessions,
2015
+ cmdEditObject,
2016
+ cmdCreate,
2017
+ cmdDeriveProjects,
2018
+ cmdLabelSessions,
2019
+ commands_exports
2020
+ };