@tekmidian/pai 0.9.0 → 0.9.2
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/dist/{auto-route-C-DrW6BL.mjs → auto-route-CruBrTf-.mjs} +2 -2
- package/dist/{auto-route-C-DrW6BL.mjs.map → auto-route-CruBrTf-.mjs.map} +1 -1
- package/dist/cli/index.mjs +345 -23
- package/dist/cli/index.mjs.map +1 -1
- package/dist/{clusters-JIDQW65f.mjs → clusters-CRlPBpq8.mjs} +1 -1
- package/dist/{clusters-JIDQW65f.mjs.map → clusters-CRlPBpq8.mjs.map} +1 -1
- package/dist/daemon/index.mjs +6 -6
- package/dist/{daemon-VIFoKc_z.mjs → daemon-kp49BE7u.mjs} +74 -21
- package/dist/daemon-kp49BE7u.mjs.map +1 -0
- package/dist/{detector-jGBuYQJM.mjs → detector-CNU3zCwP.mjs} +1 -1
- package/dist/{detector-jGBuYQJM.mjs.map → detector-CNU3zCwP.mjs.map} +1 -1
- package/dist/{factory-e0k1HWuc.mjs → factory-DKDPRhAN.mjs} +3 -3
- package/dist/{factory-e0k1HWuc.mjs.map → factory-DKDPRhAN.mjs.map} +1 -1
- package/dist/hooks/stop-hook.mjs +6 -1
- package/dist/hooks/stop-hook.mjs.map +2 -2
- package/dist/{indexer-backend-jcJFsmB4.mjs → indexer-backend-CIIlrYh6.mjs} +1 -1
- package/dist/{indexer-backend-jcJFsmB4.mjs.map → indexer-backend-CIIlrYh6.mjs.map} +1 -1
- package/dist/kg-B5ysyRLC.mjs +94 -0
- package/dist/kg-B5ysyRLC.mjs.map +1 -0
- package/dist/kg-extraction-BlGM40q7.mjs +211 -0
- package/dist/kg-extraction-BlGM40q7.mjs.map +1 -0
- package/dist/{latent-ideas-bTJo6Omd.mjs → latent-ideas-DvWBRHsy.mjs} +2 -2
- package/dist/{latent-ideas-bTJo6Omd.mjs.map → latent-ideas-DvWBRHsy.mjs.map} +1 -1
- package/dist/{neighborhood-BYYbEkUJ.mjs → neighborhood-u8ytjmWq.mjs} +1 -1
- package/dist/{neighborhood-BYYbEkUJ.mjs.map → neighborhood-u8ytjmWq.mjs.map} +1 -1
- package/dist/{note-context-BK24bX8Y.mjs → note-context-CG2_e-0W.mjs} +1 -1
- package/dist/{note-context-BK24bX8Y.mjs.map → note-context-CG2_e-0W.mjs.map} +1 -1
- package/dist/{postgres-DvEPooLO.mjs → postgres-BGERehmX.mjs} +1 -1
- package/dist/{postgres-DvEPooLO.mjs.map → postgres-BGERehmX.mjs.map} +1 -1
- package/dist/{query-feedback-Dv43XKHM.mjs → query-feedback-CQSumXDy.mjs} +1 -1
- package/dist/{query-feedback-Dv43XKHM.mjs.map → query-feedback-CQSumXDy.mjs.map} +1 -1
- package/dist/skills/Reconstruct/SKILL.md +36 -0
- package/dist/{sqlite-l-s9xPjY.mjs → sqlite-BJrME_vg.mjs} +1 -1
- package/dist/{sqlite-l-s9xPjY.mjs.map → sqlite-BJrME_vg.mjs.map} +1 -1
- package/dist/{state-C6_vqz7w.mjs → state-BIlxNRUn.mjs} +1 -1
- package/dist/{state-C6_vqz7w.mjs.map → state-BIlxNRUn.mjs.map} +1 -1
- package/dist/{themes-BvYF0W8T.mjs → themes-9jxFn3Rf.mjs} +1 -1
- package/dist/{themes-BvYF0W8T.mjs.map → themes-9jxFn3Rf.mjs.map} +1 -1
- package/dist/{tools-C4SBZHga.mjs → tools-8t7BQrm9.mjs} +13 -104
- package/dist/tools-8t7BQrm9.mjs.map +1 -0
- package/dist/{trace-CRx9lPuc.mjs → trace-C2XrzssW.mjs} +1 -1
- package/dist/{trace-CRx9lPuc.mjs.map → trace-C2XrzssW.mjs.map} +1 -1
- package/dist/{vault-indexer-B-aJpRZC.mjs → vault-indexer-TTCl1QOL.mjs} +1 -1
- package/dist/{vault-indexer-B-aJpRZC.mjs.map → vault-indexer-TTCl1QOL.mjs.map} +1 -1
- package/dist/{zettelkasten-DhBKZQHF.mjs → zettelkasten-BdaMzTGQ.mjs} +3 -3
- package/dist/{zettelkasten-DhBKZQHF.mjs.map → zettelkasten-BdaMzTGQ.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/hooks/ts/stop/stop-hook.ts +11 -1
- package/dist/daemon-VIFoKc_z.mjs.map +0 -1
- package/dist/indexer-D53l5d1U.mjs +0 -1
- package/dist/tools-C4SBZHga.mjs.map +0 -1
|
@@ -26,7 +26,7 @@ async function autoRoute(registryDb, federation, cwd, context) {
|
|
|
26
26
|
const markerResult = findMarkerUpward(registryDb, target);
|
|
27
27
|
if (markerResult) return markerResult;
|
|
28
28
|
if (context && context.trim().length > 0) {
|
|
29
|
-
const { detectTopicShift } = await import("./detector-
|
|
29
|
+
const { detectTopicShift } = await import("./detector-CNU3zCwP.mjs").then((n) => n.n);
|
|
30
30
|
const topicResult = await detectTopicShift(registryDb, federation, {
|
|
31
31
|
context,
|
|
32
32
|
threshold: .5
|
|
@@ -83,4 +83,4 @@ function formatAutoRouteJson(result) {
|
|
|
83
83
|
|
|
84
84
|
//#endregion
|
|
85
85
|
export { autoRoute, formatAutoRouteJson };
|
|
86
|
-
//# sourceMappingURL=auto-route-
|
|
86
|
+
//# sourceMappingURL=auto-route-CruBrTf-.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-route-
|
|
1
|
+
{"version":3,"file":"auto-route-CruBrTf-.mjs","names":[],"sources":["../src/session/auto-route.ts"],"sourcesContent":["/**\n * Auto-route: automatic project routing suggestion on session start.\n *\n * Given a working directory (and optional conversation context), determine\n * which registered project the session belongs to.\n *\n * Strategy (in priority order):\n * 1. Path match — exact or parent-directory match in the project registry\n * 2. Marker walk — walk up from cwd looking for Notes/PAI.md, resolve slug\n * 3. Topic match — BM25 keyword search against memory (requires context text)\n *\n * The function is stateless and works with direct DB access (no daemon\n * required), making it fast and safe to call during session startup.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport { resolve, dirname } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { readPaiMarker } from \"../registry/pai-marker.js\";\nimport { detectProject } from \"../cli/commands/detect.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type AutoRouteMethod = \"path\" | \"marker\" | \"topic\";\n\nexport interface AutoRouteResult {\n /** Project slug */\n slug: string;\n /** Human-readable project name */\n display_name: string;\n /** Absolute path to the project root */\n root_path: string;\n /** How the project was detected */\n method: AutoRouteMethod;\n /** Confidence [0,1]: 1.0 for path/marker matches, BM25 fraction for topic */\n confidence: number;\n}\n\n// ---------------------------------------------------------------------------\n// Core function\n// ---------------------------------------------------------------------------\n\n/**\n * Determine which project a session should be routed to.\n *\n * @param registryDb Open PAI registry database\n * @param federation Memory storage backend (needed only for topic fallback)\n * @param cwd Working directory to detect from (defaults to process.cwd())\n * @param context Optional conversation text for topic-based fallback\n * @returns Best project match, or null if nothing matched\n */\nexport async function autoRoute(\n registryDb: Database,\n federation: Database | StorageBackend,\n cwd?: string,\n context?: string\n): Promise<AutoRouteResult | null> {\n const target = resolve(cwd ?? process.cwd());\n\n // -------------------------------------------------------------------------\n // Strategy 1: Path match via registry\n // -------------------------------------------------------------------------\n\n const pathMatch = detectProject(registryDb, target);\n\n if (pathMatch) {\n return {\n slug: pathMatch.slug,\n display_name: pathMatch.display_name,\n root_path: pathMatch.root_path,\n method: \"path\",\n confidence: 1.0,\n };\n }\n\n // -------------------------------------------------------------------------\n // Strategy 2: PAI.md marker file walk\n //\n // Walk up from cwd, checking <dir>/Notes/PAI.md at each level.\n // Once found, resolve the slug against the registry to get full project info.\n // -------------------------------------------------------------------------\n\n const markerResult = findMarkerUpward(registryDb, target);\n if (markerResult) {\n return markerResult;\n }\n\n // -------------------------------------------------------------------------\n // Strategy 3: Topic detection (requires context text)\n // -------------------------------------------------------------------------\n\n if (context && context.trim().length > 0) {\n // Lazy import to avoid bundler pulling in daemon/index.mjs at module load time\n const { detectTopicShift } = await import(\"../topics/detector.js\");\n const topicResult = await detectTopicShift(registryDb, federation, {\n context,\n threshold: 0.5, // Lower threshold for initial routing (vs shift detection)\n });\n\n if (topicResult.suggestedProject && topicResult.confidence > 0) {\n // Look up the full project info from the registry\n const projectRow = registryDb\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'\"\n )\n .get(topicResult.suggestedProject) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (projectRow) {\n return {\n slug: projectRow.slug,\n display_name: projectRow.display_name,\n root_path: projectRow.root_path,\n method: \"topic\",\n confidence: topicResult.confidence,\n };\n }\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Marker walk helper\n// ---------------------------------------------------------------------------\n\n/**\n * Walk up the directory tree from `startDir`, checking each level for a\n * `Notes/PAI.md` file. If found, read the slug and look up the project.\n *\n * Stops at the filesystem root or after 20 levels (safety guard).\n */\nfunction findMarkerUpward(\n registryDb: Database,\n startDir: string\n): AutoRouteResult | null {\n let current = startDir;\n let depth = 0;\n\n while (depth < 20) {\n const markerPath = `${current}/Notes/PAI.md`;\n\n if (existsSync(markerPath)) {\n const marker = readPaiMarker(current);\n\n if (marker && marker.status !== \"archived\") {\n // Resolve slug to full project info in the registry\n const projectRow = registryDb\n .prepare(\n \"SELECT slug, display_name, root_path FROM projects WHERE slug = ? AND status != 'archived'\"\n )\n .get(marker.slug) as\n | { slug: string; display_name: string; root_path: string }\n | undefined;\n\n if (projectRow) {\n return {\n slug: projectRow.slug,\n display_name: projectRow.display_name,\n root_path: projectRow.root_path,\n method: \"marker\",\n confidence: 1.0,\n };\n }\n }\n }\n\n const parent = dirname(current);\n if (parent === current) break; // Reached filesystem root\n current = parent;\n depth++;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Format helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Format an AutoRouteResult as a human-readable string for CLI output.\n */\nexport function formatAutoRoute(result: AutoRouteResult): string {\n const lines: string[] = [\n `slug: ${result.slug}`,\n `display_name: ${result.display_name}`,\n `root_path: ${result.root_path}`,\n `method: ${result.method}`,\n `confidence: ${(result.confidence * 100).toFixed(0)}%`,\n ];\n return lines.join(\"\\n\");\n}\n\n/**\n * Format an AutoRouteResult as JSON for machine consumption.\n */\nexport function formatAutoRouteJson(result: AutoRouteResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsDA,eAAsB,UACpB,YACA,YACA,KACA,SACiC;CACjC,MAAM,SAAS,QAAQ,OAAO,QAAQ,KAAK,CAAC;CAM5C,MAAM,YAAY,cAAc,YAAY,OAAO;AAEnD,KAAI,UACF,QAAO;EACL,MAAM,UAAU;EAChB,cAAc,UAAU;EACxB,WAAW,UAAU;EACrB,QAAQ;EACR,YAAY;EACb;CAUH,MAAM,eAAe,iBAAiB,YAAY,OAAO;AACzD,KAAI,aACF,QAAO;AAOT,KAAI,WAAW,QAAQ,MAAM,CAAC,SAAS,GAAG;EAExC,MAAM,EAAE,qBAAqB,MAAM,OAAO;EAC1C,MAAM,cAAc,MAAM,iBAAiB,YAAY,YAAY;GACjE;GACA,WAAW;GACZ,CAAC;AAEF,MAAI,YAAY,oBAAoB,YAAY,aAAa,GAAG;GAE9D,MAAM,aAAa,WAChB,QACC,6FACD,CACA,IAAI,YAAY,iBAAiB;AAIpC,OAAI,WACF,QAAO;IACL,MAAM,WAAW;IACjB,cAAc,WAAW;IACzB,WAAW,WAAW;IACtB,QAAQ;IACR,YAAY,YAAY;IACzB;;;AAKP,QAAO;;;;;;;;AAaT,SAAS,iBACP,YACA,UACwB;CACxB,IAAI,UAAU;CACd,IAAI,QAAQ;AAEZ,QAAO,QAAQ,IAAI;AAGjB,MAAI,WAFe,GAAG,QAAQ,eAEJ,EAAE;GAC1B,MAAM,SAAS,cAAc,QAAQ;AAErC,OAAI,UAAU,OAAO,WAAW,YAAY;IAE1C,MAAM,aAAa,WAChB,QACC,6FACD,CACA,IAAI,OAAO,KAAK;AAInB,QAAI,WACF,QAAO;KACL,MAAM,WAAW;KACjB,cAAc,WAAW;KACzB,WAAW,WAAW;KACtB,QAAQ;KACR,YAAY;KACb;;;EAKP,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QAAS;AACxB,YAAU;AACV;;AAGF,QAAO;;;;;AAwBT,SAAgB,oBAAoB,QAAiC;AACnE,QAAO,KAAK,UAAU,QAAQ,MAAM,EAAE"}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -10,10 +10,11 @@ import "../embeddings-DGRAPAYb.mjs";
|
|
|
10
10
|
import { t as STOP_WORDS } from "../stop-words-BaMEGVeY.mjs";
|
|
11
11
|
import { n as populateSlugs, r as searchMemory } from "../search-DC1qhkKn.mjs";
|
|
12
12
|
import { n as formatDetection, r as formatDetectionJson, t as detectProject } from "../detect-CdaA48EI.mjs";
|
|
13
|
-
import "../
|
|
13
|
+
import { t as extractAndStoreTriples } from "../kg-extraction-BlGM40q7.mjs";
|
|
14
14
|
import { t as PaiClient } from "../ipc-client-CoyUHPod.mjs";
|
|
15
15
|
import { a as expandHome, i as ensureConfigDir, n as CONFIG_FILE$2, o as loadConfig, t as CONFIG_DIR } from "../config-BuhHWyOK.mjs";
|
|
16
|
-
import { t as createStorageBackend } from "../factory-
|
|
16
|
+
import { t as createStorageBackend } from "../factory-DKDPRhAN.mjs";
|
|
17
|
+
import { i as kgQuery } from "../kg-B5ysyRLC.mjs";
|
|
17
18
|
import { appendFileSync, chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
18
19
|
import { homedir, platform, tmpdir } from "node:os";
|
|
19
20
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
@@ -256,7 +257,7 @@ function cmdAdd(db, rawPath, opts) {
|
|
|
256
257
|
console.log(dim(` Encoded dir: ${encodedDir}`));
|
|
257
258
|
console.log(dim(` Type: ${type}`));
|
|
258
259
|
}
|
|
259
|
-
function cmdList$
|
|
260
|
+
function cmdList$3(db, opts) {
|
|
260
261
|
let query = `
|
|
261
262
|
SELECT p.*,
|
|
262
263
|
(SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count,
|
|
@@ -1099,7 +1100,7 @@ function registerProjectCommands(projectCmd, getDb) {
|
|
|
1099
1100
|
cmdAdd(getDb(), rawPath, opts);
|
|
1100
1101
|
});
|
|
1101
1102
|
projectCmd.command("list").description("List registered projects").option("--status <status>", "Filter by status: active | archived").option("--tag <tag>", "Filter by tag").option("--type <type>", "Filter by type").action((opts) => {
|
|
1102
|
-
cmdList$
|
|
1103
|
+
cmdList$3(getDb(), opts);
|
|
1103
1104
|
});
|
|
1104
1105
|
projectCmd.command("info <slug>").description("Show full details for a project").action((slug) => {
|
|
1105
1106
|
cmdInfo$1(getDb(), slug);
|
|
@@ -1350,7 +1351,7 @@ function getSessionTags(db, sessionId) {
|
|
|
1350
1351
|
|
|
1351
1352
|
//#endregion
|
|
1352
1353
|
//#region src/cli/commands/session/commands.ts
|
|
1353
|
-
function cmdList$
|
|
1354
|
+
function cmdList$2(db, projectSlug, opts) {
|
|
1354
1355
|
const limit = parseInt(opts.limit ?? "20", 10);
|
|
1355
1356
|
const params = [];
|
|
1356
1357
|
let query = `
|
|
@@ -1681,9 +1682,9 @@ function cmdActive(db, opts) {
|
|
|
1681
1682
|
], rows));
|
|
1682
1683
|
}
|
|
1683
1684
|
async function cmdAutoRoute(opts) {
|
|
1684
|
-
const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("../auto-route-
|
|
1685
|
+
const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("../auto-route-CruBrTf-.mjs");
|
|
1685
1686
|
const { openRegistry } = await import("../db-BtuN768f.mjs").then((n) => n.t);
|
|
1686
|
-
const { createStorageBackend } = await import("../factory-
|
|
1687
|
+
const { createStorageBackend } = await import("../factory-DKDPRhAN.mjs").then((n) => n.n);
|
|
1687
1688
|
const { loadConfig } = await import("../config-BuhHWyOK.mjs").then((n) => n.r);
|
|
1688
1689
|
const config = loadConfig();
|
|
1689
1690
|
const registryDb = openRegistry();
|
|
@@ -1927,7 +1928,7 @@ function cmdHandover(db, projectSlug, numberOrLatest) {
|
|
|
1927
1928
|
//#region src/cli/commands/session/index.ts
|
|
1928
1929
|
function registerSessionCommands(sessionCmd, getDb) {
|
|
1929
1930
|
sessionCmd.command("list [project-slug]").description("List sessions, optionally filtered to a single project").option("--limit <n>", "Maximum number of sessions to show", "20").option("--status <status>", "Filter by status: open | completed | compacted").action((projectSlug, opts) => {
|
|
1930
|
-
cmdList$
|
|
1931
|
+
cmdList$2(getDb(), projectSlug, opts);
|
|
1931
1932
|
});
|
|
1932
1933
|
sessionCmd.command("info <project-slug> <number>").description("Show full details for a specific session").action((projectSlug, number) => {
|
|
1933
1934
|
cmdInfo(getDb(), projectSlug, number);
|
|
@@ -2302,7 +2303,7 @@ async function countVectorDbPaths(oldPaths) {
|
|
|
2302
2303
|
if (oldPaths.length === 0) return 0;
|
|
2303
2304
|
try {
|
|
2304
2305
|
const { loadConfig } = await import("../config-BuhHWyOK.mjs").then((n) => n.r);
|
|
2305
|
-
const { PostgresBackend } = await import("../postgres-
|
|
2306
|
+
const { PostgresBackend } = await import("../postgres-BGERehmX.mjs");
|
|
2306
2307
|
const config = loadConfig();
|
|
2307
2308
|
if (config.storageBackend !== "postgres") return 0;
|
|
2308
2309
|
const pgBackend = new PostgresBackend(config.postgres ?? {});
|
|
@@ -2323,7 +2324,7 @@ async function updateVectorDbPaths(moves) {
|
|
|
2323
2324
|
if (moves.length === 0) return 0;
|
|
2324
2325
|
try {
|
|
2325
2326
|
const { loadConfig } = await import("../config-BuhHWyOK.mjs").then((n) => n.r);
|
|
2326
|
-
const { PostgresBackend } = await import("../postgres-
|
|
2327
|
+
const { PostgresBackend } = await import("../postgres-BGERehmX.mjs");
|
|
2327
2328
|
const config = loadConfig();
|
|
2328
2329
|
if (config.storageBackend !== "postgres") return 0;
|
|
2329
2330
|
const pgBackend = new PostgresBackend(config.postgres ?? {});
|
|
@@ -2971,7 +2972,7 @@ function cmdMigrate$1(db) {
|
|
|
2971
2972
|
|
|
2972
2973
|
//#endregion
|
|
2973
2974
|
//#region src/cli/commands/registry/index.ts
|
|
2974
|
-
function cmdStats$
|
|
2975
|
+
function cmdStats$2(db) {
|
|
2975
2976
|
const totalProjects = db.prepare("SELECT COUNT(*) AS n FROM projects").get().n;
|
|
2976
2977
|
const activeProjects = db.prepare("SELECT COUNT(*) AS n FROM projects WHERE status = 'active'").get().n;
|
|
2977
2978
|
const archivedProjects = db.prepare("SELECT COUNT(*) AS n FROM projects WHERE status = 'archived'").get().n;
|
|
@@ -3058,7 +3059,7 @@ function registerRegistryCommands(registryCmd, getDb) {
|
|
|
3058
3059
|
cmdMigrate$1(getDb());
|
|
3059
3060
|
});
|
|
3060
3061
|
registryCmd.command("stats").description("Show summary statistics for the registry").action(() => {
|
|
3061
|
-
cmdStats$
|
|
3062
|
+
cmdStats$2(getDb());
|
|
3062
3063
|
});
|
|
3063
3064
|
registryCmd.command("rebuild").description("Erase all registry data and rebuild from the filesystem (destructive)").action(() => {
|
|
3064
3065
|
cmdRebuild(getDb());
|
|
@@ -3793,7 +3794,7 @@ function cmdLogs(opts) {
|
|
|
3793
3794
|
}
|
|
3794
3795
|
function registerDaemonCommands(daemonCmd) {
|
|
3795
3796
|
daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
|
|
3796
|
-
const { serve } = await import("../daemon-
|
|
3797
|
+
const { serve } = await import("../daemon-kp49BE7u.mjs").then((n) => n.t);
|
|
3797
3798
|
const { loadConfig: lc, ensureConfigDir } = await import("../config-BuhHWyOK.mjs").then((n) => n.r);
|
|
3798
3799
|
ensureConfigDir();
|
|
3799
3800
|
await serve(lc());
|
|
@@ -6411,7 +6412,7 @@ async function cmdExplore(note, opts) {
|
|
|
6411
6412
|
const depth = parseInt(opts.depth ?? "3", 10);
|
|
6412
6413
|
const direction = opts.direction ?? "both";
|
|
6413
6414
|
const mode = opts.mode ?? "all";
|
|
6414
|
-
const { zettelExplore } = await import("../zettelkasten-
|
|
6415
|
+
const { zettelExplore } = await import("../zettelkasten-BdaMzTGQ.mjs");
|
|
6415
6416
|
const result = zettelExplore(getFedDb(), {
|
|
6416
6417
|
startNote: note,
|
|
6417
6418
|
depth,
|
|
@@ -6475,7 +6476,7 @@ async function cmdHealth(opts) {
|
|
|
6475
6476
|
const projectPath = opts.project;
|
|
6476
6477
|
const recentDays = parseInt(opts.days ?? "30", 10);
|
|
6477
6478
|
const includeTypes = opts.include ? opts.include.split(",").map((s) => s.trim()) : void 0;
|
|
6478
|
-
const { zettelHealth } = await import("../zettelkasten-
|
|
6479
|
+
const { zettelHealth } = await import("../zettelkasten-BdaMzTGQ.mjs");
|
|
6479
6480
|
const result = zettelHealth(getFedDb(), {
|
|
6480
6481
|
scope,
|
|
6481
6482
|
projectPath,
|
|
@@ -6544,7 +6545,7 @@ async function cmdSurprise(note, opts) {
|
|
|
6544
6545
|
const limit = parseInt(opts.limit ?? "10", 10);
|
|
6545
6546
|
const minSimilarity = parseFloat(opts.minSimilarity ?? "0.3");
|
|
6546
6547
|
const minGraphDistance = parseInt(opts.minDistance ?? "3", 10);
|
|
6547
|
-
const { zettelSurprise } = await import("../zettelkasten-
|
|
6548
|
+
const { zettelSurprise } = await import("../zettelkasten-BdaMzTGQ.mjs");
|
|
6548
6549
|
const db = getFedDb();
|
|
6549
6550
|
console.log();
|
|
6550
6551
|
console.log(header(" PAI Zettel Surprise"));
|
|
@@ -6597,7 +6598,7 @@ async function cmdSuggest(note, opts) {
|
|
|
6597
6598
|
const vaultProjectId = parseInt(opts.vaultProjectId, 10);
|
|
6598
6599
|
const limit = parseInt(opts.limit ?? "5", 10);
|
|
6599
6600
|
const excludeLinked = opts.excludeLinked !== false;
|
|
6600
|
-
const { zettelSuggest } = await import("../zettelkasten-
|
|
6601
|
+
const { zettelSuggest } = await import("../zettelkasten-BdaMzTGQ.mjs");
|
|
6601
6602
|
const db = getFedDb();
|
|
6602
6603
|
console.log();
|
|
6603
6604
|
console.log(header(" PAI Zettel Suggest"));
|
|
@@ -6648,7 +6649,7 @@ async function cmdConverse(question, opts) {
|
|
|
6648
6649
|
const vaultProjectId = parseInt(opts.vaultProjectId, 10);
|
|
6649
6650
|
const depth = parseInt(opts.depth ?? "2", 10);
|
|
6650
6651
|
const limit = parseInt(opts.limit ?? "15", 10);
|
|
6651
|
-
const { zettelConverse } = await import("../zettelkasten-
|
|
6652
|
+
const { zettelConverse } = await import("../zettelkasten-BdaMzTGQ.mjs");
|
|
6652
6653
|
const db = getFedDb();
|
|
6653
6654
|
console.log();
|
|
6654
6655
|
console.log(header(" PAI Zettel Converse"));
|
|
@@ -6709,7 +6710,7 @@ async function cmdThemes(opts) {
|
|
|
6709
6710
|
const minClusterSize = parseInt(opts.minSize ?? "3", 10);
|
|
6710
6711
|
const maxThemes = parseInt(opts.maxThemes ?? "10", 10);
|
|
6711
6712
|
const similarityThreshold = parseFloat(opts.threshold ?? "0.65");
|
|
6712
|
-
const { zettelThemes } = await import("../zettelkasten-
|
|
6713
|
+
const { zettelThemes } = await import("../zettelkasten-BdaMzTGQ.mjs");
|
|
6713
6714
|
const db = getFedDb();
|
|
6714
6715
|
console.log();
|
|
6715
6716
|
console.log(header(" PAI Zettel Themes"));
|
|
@@ -6838,7 +6839,7 @@ function trunc(s, maxLen) {
|
|
|
6838
6839
|
if (s.length <= maxLen) return s;
|
|
6839
6840
|
return s.slice(0, maxLen - 1) + "…";
|
|
6840
6841
|
}
|
|
6841
|
-
async function cmdList(opts) {
|
|
6842
|
+
async function cmdList$1(opts) {
|
|
6842
6843
|
const limit = parseInt(opts.limit ?? "20", 10);
|
|
6843
6844
|
const params = { limit };
|
|
6844
6845
|
if (opts.project) params.project_slug = opts.project;
|
|
@@ -6935,7 +6936,7 @@ async function cmdSearch(query, opts) {
|
|
|
6935
6936
|
console.log(dim(` ${observations.length} result(s)`));
|
|
6936
6937
|
console.log();
|
|
6937
6938
|
}
|
|
6938
|
-
async function cmdStats() {
|
|
6939
|
+
async function cmdStats$1() {
|
|
6939
6940
|
let stats;
|
|
6940
6941
|
try {
|
|
6941
6942
|
stats = await ipcCall("observation_stats", {});
|
|
@@ -6984,7 +6985,7 @@ async function cmdStats() {
|
|
|
6984
6985
|
function registerObservationCommands(parent) {
|
|
6985
6986
|
parent.command("list").description("List recent observations").option("--project <slug>", "Filter by project slug").option("--type <type>", "Filter by type (decision, bugfix, feature, refactor, discovery, change)").option("--session <id>", "Filter by session ID").option("--limit <n>", "Maximum results", "20").action(async (opts) => {
|
|
6986
6987
|
try {
|
|
6987
|
-
await cmdList(opts);
|
|
6988
|
+
await cmdList$1(opts);
|
|
6988
6989
|
} catch (e) {
|
|
6989
6990
|
console.error(err(` Error: ${e}`));
|
|
6990
6991
|
process.exit(1);
|
|
@@ -7000,7 +7001,7 @@ function registerObservationCommands(parent) {
|
|
|
7000
7001
|
});
|
|
7001
7002
|
parent.command("stats").description("Show observation statistics: totals, by type, by project").action(async () => {
|
|
7002
7003
|
try {
|
|
7003
|
-
await cmdStats();
|
|
7004
|
+
await cmdStats$1();
|
|
7004
7005
|
} catch (e) {
|
|
7005
7006
|
console.error(err(` Error: ${e}`));
|
|
7006
7007
|
process.exit(1);
|
|
@@ -7569,6 +7570,326 @@ function registerTopicCommands(topicCmd) {
|
|
|
7569
7570
|
});
|
|
7570
7571
|
}
|
|
7571
7572
|
|
|
7573
|
+
//#endregion
|
|
7574
|
+
//#region src/memory/kg-backfill.ts
|
|
7575
|
+
/**
|
|
7576
|
+
* kg-backfill.ts — Populate the temporal knowledge graph from existing
|
|
7577
|
+
* session notes.
|
|
7578
|
+
*
|
|
7579
|
+
* Walks `Notes/YYYY/MM/*.md` for each registered project, runs the same
|
|
7580
|
+
* extractor that the session-summary-worker uses on every NEW summary, and
|
|
7581
|
+
* stores triples in Postgres. Idempotent: a state file at
|
|
7582
|
+
* ~/.config/pai/kg-backfill-state.json records which note paths have been
|
|
7583
|
+
* processed so re-runs skip them.
|
|
7584
|
+
*
|
|
7585
|
+
* Even without the state file the operation is safe — extractAndStoreTriples
|
|
7586
|
+
* uses supersession logic so re-extracting a note never produces duplicates.
|
|
7587
|
+
*/
|
|
7588
|
+
const STATE_FILE = join(homedir(), ".config", "pai", "kg-backfill-state.json");
|
|
7589
|
+
function loadState() {
|
|
7590
|
+
try {
|
|
7591
|
+
if (existsSync(STATE_FILE)) {
|
|
7592
|
+
const raw = JSON.parse(readFileSync(STATE_FILE, "utf-8"));
|
|
7593
|
+
if (raw && typeof raw === "object" && raw.processed) return raw;
|
|
7594
|
+
}
|
|
7595
|
+
} catch {}
|
|
7596
|
+
return { processed: {} };
|
|
7597
|
+
}
|
|
7598
|
+
function saveState(state) {
|
|
7599
|
+
try {
|
|
7600
|
+
const dir = join(homedir(), ".config", "pai");
|
|
7601
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
7602
|
+
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), "utf-8");
|
|
7603
|
+
} catch (e) {
|
|
7604
|
+
process.stderr.write(`[kg-backfill] failed to save state: ${e}\n`);
|
|
7605
|
+
}
|
|
7606
|
+
}
|
|
7607
|
+
/**
|
|
7608
|
+
* Find all session notes under <project_root>/Notes/YYYY/MM/*.md.
|
|
7609
|
+
* Falls back to <project_root>/Notes/*.md (flat layout) when no year/month
|
|
7610
|
+
* subdirectories are present.
|
|
7611
|
+
*/
|
|
7612
|
+
function findProjectNotes(project) {
|
|
7613
|
+
const notesRoot = join(project.root_path, "Notes");
|
|
7614
|
+
if (!existsSync(notesRoot)) return [];
|
|
7615
|
+
const found = [];
|
|
7616
|
+
function walk(dir, depth) {
|
|
7617
|
+
if (depth > 4) return;
|
|
7618
|
+
let entries;
|
|
7619
|
+
try {
|
|
7620
|
+
entries = readdirSync(dir);
|
|
7621
|
+
} catch {
|
|
7622
|
+
return;
|
|
7623
|
+
}
|
|
7624
|
+
for (const name of entries) {
|
|
7625
|
+
const full = join(dir, name);
|
|
7626
|
+
let st;
|
|
7627
|
+
try {
|
|
7628
|
+
st = statSync(full);
|
|
7629
|
+
} catch {
|
|
7630
|
+
continue;
|
|
7631
|
+
}
|
|
7632
|
+
if (st.isDirectory()) walk(full, depth + 1);
|
|
7633
|
+
else if (st.isFile() && name.endsWith(".md") && /^\d{3,4}/.test(name)) found.push({
|
|
7634
|
+
path: full,
|
|
7635
|
+
mtime: st.mtimeMs
|
|
7636
|
+
});
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
walk(notesRoot, 0);
|
|
7640
|
+
found.sort((a, b) => a.mtime - b.mtime);
|
|
7641
|
+
return found.map((f) => f.path);
|
|
7642
|
+
}
|
|
7643
|
+
/**
|
|
7644
|
+
* Backfill the knowledge graph from existing session notes.
|
|
7645
|
+
*
|
|
7646
|
+
* Requires the Postgres backend (KG tables live there). Throws if Postgres
|
|
7647
|
+
* is unavailable, since this is an explicit user action.
|
|
7648
|
+
*/
|
|
7649
|
+
async function backfillKgFromNotes(options = {}) {
|
|
7650
|
+
const result = {
|
|
7651
|
+
notes_processed: 0,
|
|
7652
|
+
triples_extracted: 0,
|
|
7653
|
+
triples_added: 0,
|
|
7654
|
+
triples_superseded: 0,
|
|
7655
|
+
errors: 0
|
|
7656
|
+
};
|
|
7657
|
+
const config = loadConfig();
|
|
7658
|
+
if (config.storageBackend !== "postgres") throw new Error("kg backfill requires the Postgres backend. Set \"storageBackend\": \"postgres\" in ~/.config/pai/config.json.");
|
|
7659
|
+
const backend = await createStorageBackend(config);
|
|
7660
|
+
if (backend.backendType !== "postgres") throw new Error("Postgres backend unavailable — fell back to SQLite. Cannot backfill KG.");
|
|
7661
|
+
const pool = backend.getPool();
|
|
7662
|
+
const registry = openRegistry();
|
|
7663
|
+
let projects;
|
|
7664
|
+
try {
|
|
7665
|
+
if (options.projectSlug) {
|
|
7666
|
+
const row = registry.prepare("SELECT id, slug, root_path FROM projects WHERE slug = ?").get(options.projectSlug);
|
|
7667
|
+
if (!row) throw new Error(`Project not found: ${options.projectSlug}`);
|
|
7668
|
+
projects = [row];
|
|
7669
|
+
} else projects = registry.prepare("SELECT id, slug, root_path FROM projects WHERE status = 'active' ORDER BY slug").all();
|
|
7670
|
+
} finally {
|
|
7671
|
+
registry.close();
|
|
7672
|
+
}
|
|
7673
|
+
const state = loadState();
|
|
7674
|
+
const work = [];
|
|
7675
|
+
for (const project of projects) {
|
|
7676
|
+
const notes = findProjectNotes(project);
|
|
7677
|
+
for (const notePath of notes) {
|
|
7678
|
+
if (state.processed[notePath]) continue;
|
|
7679
|
+
work.push({
|
|
7680
|
+
project,
|
|
7681
|
+
notePath
|
|
7682
|
+
});
|
|
7683
|
+
}
|
|
7684
|
+
}
|
|
7685
|
+
const total = options.limit ? Math.min(options.limit, work.length) : work.length;
|
|
7686
|
+
for (let i = 0; i < total; i++) {
|
|
7687
|
+
const { project, notePath } = work[i];
|
|
7688
|
+
options.onProgress?.(i + 1, total, notePath);
|
|
7689
|
+
if (options.dryRun) {
|
|
7690
|
+
result.notes_processed++;
|
|
7691
|
+
continue;
|
|
7692
|
+
}
|
|
7693
|
+
let noteContent;
|
|
7694
|
+
try {
|
|
7695
|
+
noteContent = readFileSync(notePath, "utf-8");
|
|
7696
|
+
} catch (e) {
|
|
7697
|
+
process.stderr.write(`[kg-backfill] read failed ${notePath}: ${e}\n`);
|
|
7698
|
+
result.errors++;
|
|
7699
|
+
continue;
|
|
7700
|
+
}
|
|
7701
|
+
try {
|
|
7702
|
+
const stats = await extractAndStoreTriples(pool, {
|
|
7703
|
+
summaryText: noteContent,
|
|
7704
|
+
projectSlug: project.slug,
|
|
7705
|
+
projectId: project.id,
|
|
7706
|
+
sessionId: `backfill:${notePath}`,
|
|
7707
|
+
gitLog: "",
|
|
7708
|
+
model: "sonnet"
|
|
7709
|
+
});
|
|
7710
|
+
result.notes_processed++;
|
|
7711
|
+
result.triples_extracted += stats.extracted;
|
|
7712
|
+
result.triples_added += stats.added;
|
|
7713
|
+
result.triples_superseded += stats.superseded;
|
|
7714
|
+
state.processed[notePath] = (/* @__PURE__ */ new Date()).toISOString();
|
|
7715
|
+
if ((i + 1) % 5 === 0) saveState(state);
|
|
7716
|
+
} catch (e) {
|
|
7717
|
+
process.stderr.write(`[kg-backfill] extract failed ${notePath}: ${e}\n`);
|
|
7718
|
+
result.errors++;
|
|
7719
|
+
}
|
|
7720
|
+
}
|
|
7721
|
+
if (!options.dryRun) saveState(state);
|
|
7722
|
+
await backend.close();
|
|
7723
|
+
return result;
|
|
7724
|
+
}
|
|
7725
|
+
|
|
7726
|
+
//#endregion
|
|
7727
|
+
//#region src/cli/commands/kg.ts
|
|
7728
|
+
async function getPool() {
|
|
7729
|
+
const config = loadConfig();
|
|
7730
|
+
if (config.storageBackend !== "postgres") {
|
|
7731
|
+
console.error(err(" KG commands require Postgres backend."));
|
|
7732
|
+
console.error(dim(" Set \"storageBackend\": \"postgres\" in ~/.config/pai/config.json"));
|
|
7733
|
+
process.exit(1);
|
|
7734
|
+
}
|
|
7735
|
+
const backend = await createStorageBackend(config);
|
|
7736
|
+
if (backend.backendType !== "postgres") {
|
|
7737
|
+
console.error(err(" Postgres backend unavailable — fell back to SQLite."));
|
|
7738
|
+
process.exit(1);
|
|
7739
|
+
}
|
|
7740
|
+
return {
|
|
7741
|
+
pool: backend.getPool(),
|
|
7742
|
+
close: () => backend.close()
|
|
7743
|
+
};
|
|
7744
|
+
}
|
|
7745
|
+
function shorten(s, n) {
|
|
7746
|
+
return s.length <= n ? s : s.slice(0, n - 1) + "…";
|
|
7747
|
+
}
|
|
7748
|
+
async function cmdBackfill(opts) {
|
|
7749
|
+
const limit = opts.limit ? parseInt(opts.limit, 10) : void 0;
|
|
7750
|
+
if (limit !== void 0 && (isNaN(limit) || limit < 1)) {
|
|
7751
|
+
console.error(err(" --limit must be a positive integer"));
|
|
7752
|
+
process.exit(1);
|
|
7753
|
+
}
|
|
7754
|
+
console.log();
|
|
7755
|
+
console.log(header(" PAI KG Backfill"));
|
|
7756
|
+
console.log();
|
|
7757
|
+
if (opts.project) console.log(` ${bold("Project:")} ${opts.project}`);
|
|
7758
|
+
if (limit) console.log(` ${bold("Limit:")} ${limit}`);
|
|
7759
|
+
if (opts.dryRun) console.log(` ${bold("Mode:")} ${warn("dry-run")}`);
|
|
7760
|
+
console.log();
|
|
7761
|
+
try {
|
|
7762
|
+
const result = await backfillKgFromNotes({
|
|
7763
|
+
projectSlug: opts.project,
|
|
7764
|
+
limit,
|
|
7765
|
+
dryRun: opts.dryRun,
|
|
7766
|
+
onProgress: (current, total, note) => {
|
|
7767
|
+
const short = shorten(note.replace(process.env.HOME ?? "", "~"), 70);
|
|
7768
|
+
process.stdout.write(` [${current}/${total}] ${dim(short)}\n`);
|
|
7769
|
+
}
|
|
7770
|
+
});
|
|
7771
|
+
console.log();
|
|
7772
|
+
console.log(ok(" Backfill complete"));
|
|
7773
|
+
console.log(` Notes processed: ${bold(String(result.notes_processed))}`);
|
|
7774
|
+
console.log(` Triples extracted: ${bold(String(result.triples_extracted))}`);
|
|
7775
|
+
console.log(` Triples added: ${bold(String(result.triples_added))}`);
|
|
7776
|
+
console.log(` Triples superseded: ${bold(String(result.triples_superseded))}`);
|
|
7777
|
+
if (result.errors > 0) console.log(` ${warn("Errors:")} ${result.errors}`);
|
|
7778
|
+
console.log();
|
|
7779
|
+
} catch (e) {
|
|
7780
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
7781
|
+
console.error(err(` ${msg}`));
|
|
7782
|
+
process.exit(1);
|
|
7783
|
+
}
|
|
7784
|
+
}
|
|
7785
|
+
async function cmdQuery(opts) {
|
|
7786
|
+
const { pool, close } = await getPool();
|
|
7787
|
+
try {
|
|
7788
|
+
let projectId;
|
|
7789
|
+
if (opts.project) {
|
|
7790
|
+
const r = await pool.query("SELECT id FROM projects WHERE slug = $1 LIMIT 1", [opts.project]);
|
|
7791
|
+
if (r.rows.length === 0) console.error(warn(` Project not found in Postgres: ${opts.project}`));
|
|
7792
|
+
else projectId = r.rows[0].id;
|
|
7793
|
+
}
|
|
7794
|
+
const asOf = opts.asOf ? new Date(opts.asOf) : void 0;
|
|
7795
|
+
if (asOf && isNaN(asOf.getTime())) {
|
|
7796
|
+
console.error(err(` Invalid --as-of date: ${opts.asOf}`));
|
|
7797
|
+
process.exit(1);
|
|
7798
|
+
}
|
|
7799
|
+
const triples = await kgQuery(pool, {
|
|
7800
|
+
subject: opts.subject,
|
|
7801
|
+
predicate: opts.predicate,
|
|
7802
|
+
object: opts.object,
|
|
7803
|
+
project_id: projectId,
|
|
7804
|
+
as_of: asOf
|
|
7805
|
+
});
|
|
7806
|
+
if (opts.json) {
|
|
7807
|
+
console.log(JSON.stringify(triples, null, 2));
|
|
7808
|
+
return;
|
|
7809
|
+
}
|
|
7810
|
+
console.log();
|
|
7811
|
+
console.log(header(` ${triples.length} triple(s)`));
|
|
7812
|
+
console.log();
|
|
7813
|
+
for (const t of triples) {
|
|
7814
|
+
const validity = t.valid_to ? dim(`(invalidated ${t.valid_to.toISOString().slice(0, 10)})`) : dim(`(valid since ${t.valid_from.toISOString().slice(0, 10)})`);
|
|
7815
|
+
console.log(` ${bold(t.subject)} ${dim("·")} ${t.predicate} ${dim("·")} ${t.object} ${validity}`);
|
|
7816
|
+
}
|
|
7817
|
+
console.log();
|
|
7818
|
+
} finally {
|
|
7819
|
+
await close();
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7822
|
+
async function cmdList(opts) {
|
|
7823
|
+
const limit = opts.limit ? parseInt(opts.limit, 10) : 50;
|
|
7824
|
+
const { pool, close } = await getPool();
|
|
7825
|
+
try {
|
|
7826
|
+
let projectId;
|
|
7827
|
+
if (opts.project) {
|
|
7828
|
+
const r = await pool.query("SELECT id FROM projects WHERE slug = $1 LIMIT 1", [opts.project]);
|
|
7829
|
+
if (r.rows.length > 0) projectId = r.rows[0].id;
|
|
7830
|
+
}
|
|
7831
|
+
const triples = await kgQuery(pool, { project_id: projectId });
|
|
7832
|
+
const slice = triples.slice(0, limit);
|
|
7833
|
+
console.log();
|
|
7834
|
+
console.log(header(` ${slice.length} of ${triples.length} currently-valid triple(s)`));
|
|
7835
|
+
console.log();
|
|
7836
|
+
for (const t of slice) console.log(` ${bold(t.subject)} ${dim("·")} ${t.predicate} ${dim("·")} ${t.object}`);
|
|
7837
|
+
if (triples.length > slice.length) {
|
|
7838
|
+
console.log();
|
|
7839
|
+
console.log(dim(` (${triples.length - slice.length} more — increase --limit)`));
|
|
7840
|
+
}
|
|
7841
|
+
console.log();
|
|
7842
|
+
} finally {
|
|
7843
|
+
await close();
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7846
|
+
async function cmdStats() {
|
|
7847
|
+
const { pool, close } = await getPool();
|
|
7848
|
+
try {
|
|
7849
|
+
const totals = await pool.query(`SELECT
|
|
7850
|
+
COUNT(*)::text AS total,
|
|
7851
|
+
COUNT(*) FILTER (WHERE valid_to IS NULL)::text AS valid,
|
|
7852
|
+
COUNT(*) FILTER (WHERE valid_to IS NOT NULL)::text AS invalidated,
|
|
7853
|
+
COUNT(DISTINCT subject)::text AS subjects,
|
|
7854
|
+
COUNT(DISTINCT predicate)::text AS predicates
|
|
7855
|
+
FROM kg_triples`);
|
|
7856
|
+
const contradictions = await pool.query(`SELECT COUNT(*)::text AS count FROM (
|
|
7857
|
+
SELECT subject, predicate
|
|
7858
|
+
FROM kg_triples
|
|
7859
|
+
WHERE valid_to IS NULL
|
|
7860
|
+
GROUP BY subject, predicate
|
|
7861
|
+
HAVING COUNT(*) > 1
|
|
7862
|
+
) c`);
|
|
7863
|
+
const row = totals.rows[0] ?? {};
|
|
7864
|
+
console.log();
|
|
7865
|
+
console.log(header(" PAI KG Stats"));
|
|
7866
|
+
console.log();
|
|
7867
|
+
console.log(` ${bold("Total triples:")} ${row.total ?? "0"}`);
|
|
7868
|
+
console.log(` ${bold("Currently valid:")} ${row.valid ?? "0"}`);
|
|
7869
|
+
console.log(` ${bold("Invalidated:")} ${row.invalidated ?? "0"}`);
|
|
7870
|
+
console.log(` ${bold("Distinct subjects:")} ${row.subjects ?? "0"}`);
|
|
7871
|
+
console.log(` ${bold("Distinct predicates:")} ${row.predicates ?? "0"}`);
|
|
7872
|
+
console.log(` ${bold("Contradictions:")} ${contradictions.rows[0]?.count ?? "0"}`);
|
|
7873
|
+
console.log();
|
|
7874
|
+
} finally {
|
|
7875
|
+
await close();
|
|
7876
|
+
}
|
|
7877
|
+
}
|
|
7878
|
+
function registerKgCommands(kgCmd) {
|
|
7879
|
+
kgCmd.command("backfill").description("Populate the KG from existing session notes (idempotent)").option("--project <slug>", "Restrict backfill to a single project").option("--limit <n>", "Maximum number of notes to process").option("--dry-run", "List notes that would be processed without extracting").action(async (opts) => {
|
|
7880
|
+
await cmdBackfill(opts);
|
|
7881
|
+
});
|
|
7882
|
+
kgCmd.command("query").description("Query KG triples by subject, predicate, object, time, or project").option("--subject <s>", "Filter by subject").option("--predicate <p>", "Filter by predicate").option("--object <o>", "Filter by object").option("--as-of <date>", "Point-in-time query (YYYY-MM-DD or ISO 8601)").option("--project <slug>", "Restrict to a project slug").option("--json", "Output raw JSON").action(async (opts) => {
|
|
7883
|
+
await cmdQuery(opts);
|
|
7884
|
+
});
|
|
7885
|
+
kgCmd.command("list").description("List currently-valid triples").option("--project <slug>", "Restrict to a project slug").option("--limit <n>", "Maximum triples to print", "50").action(async (opts) => {
|
|
7886
|
+
await cmdList(opts);
|
|
7887
|
+
});
|
|
7888
|
+
kgCmd.command("stats").description("Show triple counts and contradiction count").action(async () => {
|
|
7889
|
+
await cmdStats();
|
|
7890
|
+
});
|
|
7891
|
+
}
|
|
7892
|
+
|
|
7572
7893
|
//#endregion
|
|
7573
7894
|
//#region src/cli/index.ts
|
|
7574
7895
|
/**
|
|
@@ -7617,6 +7938,7 @@ registerSetupCommand(program);
|
|
|
7617
7938
|
registerUpdateCommand(program);
|
|
7618
7939
|
registerNotifyCommands(program.command("notify").description("Notification config: status, get, set, test, send"));
|
|
7619
7940
|
registerTopicCommands(program.command("topic").description("Topic shift detection: check whether context has drifted to a different project"));
|
|
7941
|
+
registerKgCommands(program.command("kg").description("Temporal knowledge graph: backfill, query, list, stats"));
|
|
7620
7942
|
registerObsidianCommands(program.command("obsidian").description("Obsidian vault: sync project notes, view status, open in Obsidian"), getDb);
|
|
7621
7943
|
registerZettelCommands(program.command("zettel").description("Zettelkasten intelligence: explore, surprise, converse, themes, health, suggest"), getDb);
|
|
7622
7944
|
registerObservationCommands(program.command("observation").description("Observation capture: list, search, and stats"));
|