@goondocks/myco 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -0
- package/dist/{chunk-JJL6AMDA.js → chunk-24DOZEUJ.js} +255 -6
- package/dist/chunk-24DOZEUJ.js.map +1 -0
- package/dist/{chunk-ZWUFTOG3.js → chunk-2GSX3BK2.js} +4 -4
- package/dist/{chunk-FIRMTYFH.js → chunk-2YBUL3IL.js} +4 -37
- package/dist/chunk-2YBUL3IL.js.map +1 -0
- package/dist/{chunk-HL2S5QZG.js → chunk-2ZBB3MQT.js} +319 -40
- package/dist/chunk-2ZBB3MQT.js.map +1 -0
- package/dist/{chunk-HJG7Z6SJ.js → chunk-3EM23DMD.js} +2 -2
- package/dist/{chunk-XQXXF6MU.js → chunk-4RMSHZE4.js} +12 -1
- package/dist/{chunk-XQXXF6MU.js.map → chunk-4RMSHZE4.js.map} +1 -1
- package/dist/{chunk-T7OC6GH5.js → chunk-5FNZ7AMX.js} +2 -2
- package/dist/{chunk-X6TKHO22.js → chunk-5QWZT4AB.js} +2 -2
- package/dist/{chunk-B6WVNDA5.js → chunk-6BSDCZ5Q.js} +8 -2
- package/dist/{chunk-B6WVNDA5.js.map → chunk-6BSDCZ5Q.js.map} +1 -1
- package/dist/{chunk-R6LQT3U7.js → chunk-B5UZSHQV.js} +8 -12
- package/dist/{chunk-R6LQT3U7.js.map → chunk-B5UZSHQV.js.map} +1 -1
- package/dist/{chunk-7KQB22DP.js → chunk-E7OBRBCQ.js} +2 -2
- package/dist/{chunk-RCV2I4AI.js → chunk-GDYYJTTT.js} +5 -3
- package/dist/{chunk-RCV2I4AI.js.map → chunk-GDYYJTTT.js.map} +1 -1
- package/dist/{chunk-MIU3DKLN.js → chunk-GNR3QAER.js} +2 -2
- package/dist/{chunk-BMJX2IDQ.js → chunk-H7PRCVGQ.js} +2 -2
- package/dist/{chunk-6LTNFMXO.js → chunk-KC7ENQTN.js} +2 -2
- package/dist/chunk-KUMVJIJW.js +117 -0
- package/dist/chunk-KUMVJIJW.js.map +1 -0
- package/dist/{chunk-ND4VK6C7.js → chunk-L25U7PIG.js} +2 -2
- package/dist/{chunk-6UJWI4IW.js → chunk-MQSYSQ6T.js} +7 -5
- package/dist/{chunk-6UJWI4IW.js.map → chunk-MQSYSQ6T.js.map} +1 -1
- package/dist/{chunk-TBRZAJ7W.js → chunk-P3WO3N3I.js} +11 -3
- package/dist/chunk-P3WO3N3I.js.map +1 -0
- package/dist/{chunk-JI6M2L2W.js → chunk-QGJ2ZIUZ.js} +7 -4
- package/dist/chunk-QGJ2ZIUZ.js.map +1 -0
- package/dist/{chunk-5EZ7QF6J.js → chunk-QLUE3BUL.js} +66 -1
- package/dist/chunk-QLUE3BUL.js.map +1 -0
- package/dist/{chunk-AK6GNLPV.js → chunk-TWSTAVLO.js} +17 -1
- package/dist/{chunk-AK6GNLPV.js.map → chunk-TWSTAVLO.js.map} +1 -1
- package/dist/{chunk-FIA5NTRH.js → chunk-UVGAVYWZ.js} +11 -13
- package/dist/chunk-UVGAVYWZ.js.map +1 -0
- package/dist/{chunk-UKWO26VI.js → chunk-YTANWAGE.js} +2 -2
- package/dist/chunk-ZMYNRTTD.js +64 -0
- package/dist/chunk-ZMYNRTTD.js.map +1 -0
- package/dist/{cli-BLYNNKGJ.js → cli-K7SUTP7A.js} +22 -22
- package/dist/{client-5GB4WVXE.js → client-YJMNTITQ.js} +5 -5
- package/dist/{config-5FGLQGCW.js → config-G5GGT5A6.js} +3 -3
- package/dist/curate-6T5NKVXK.js +80 -0
- package/dist/curate-6T5NKVXK.js.map +1 -0
- package/dist/{detect-providers-BIHYFK5M.js → detect-providers-S3M5TAMW.js} +3 -3
- package/dist/{digest-7NKYXM6G.js → digest-O35VHYFP.js} +31 -40
- package/dist/digest-O35VHYFP.js.map +1 -0
- package/dist/{init-HPQ77WWF.js → init-TFLSATB3.js} +9 -11
- package/dist/init-TFLSATB3.js.map +1 -0
- package/dist/{logs-BSTBZHDR.js → logs-IENORIYR.js} +3 -3
- package/dist/{main-NFQ4II75.js → main-JEUQS3BY.js} +1218 -294
- package/dist/main-JEUQS3BY.js.map +1 -0
- package/dist/rebuild-7SH5GSNX.js +66 -0
- package/dist/rebuild-7SH5GSNX.js.map +1 -0
- package/dist/{reprocess-ZL4HKTSC.js → reprocess-Q4YH2ZBK.js} +20 -22
- package/dist/{reprocess-ZL4HKTSC.js.map → reprocess-Q4YH2ZBK.js.map} +1 -1
- package/dist/{restart-FYW662DR.js → restart-NLJLB52D.js} +7 -6
- package/dist/{restart-FYW662DR.js.map → restart-NLJLB52D.js.map} +1 -1
- package/dist/{search-E5JQMTXV.js → search-2BVRF54H.js} +10 -10
- package/dist/{server-TV3D35HZ.js → server-4AMZNP4F.js} +51 -97
- package/dist/{server-TV3D35HZ.js.map → server-4AMZNP4F.js.map} +1 -1
- package/dist/{session-QF6MILAC.js → session-F326AWCH.js} +2 -2
- package/dist/{session-start-5MFEOVQ5.js → session-start-AZAF3DTE.js} +10 -10
- package/dist/setup-digest-YLZZGSSR.js +15 -0
- package/dist/setup-llm-JOXBSLXC.js +15 -0
- package/dist/src/cli.js +4 -4
- package/dist/src/daemon/main.js +4 -4
- package/dist/src/hooks/post-tool-use.js +5 -5
- package/dist/src/hooks/session-end.js +5 -5
- package/dist/src/hooks/session-start.js +4 -4
- package/dist/src/hooks/stop.js +7 -7
- package/dist/src/hooks/user-prompt-submit.js +5 -5
- package/dist/src/mcp/server.js +4 -4
- package/dist/src/prompts/consolidation.md +46 -0
- package/dist/src/templates/portal.md +5 -0
- package/dist/stats-MKDIZFIQ.js +58 -0
- package/dist/stats-MKDIZFIQ.js.map +1 -0
- package/dist/templates-XPRBOWCE.js +38 -0
- package/dist/templates-XPRBOWCE.js.map +1 -0
- package/dist/ui/assets/index-D37IoDXS.css +1 -0
- package/dist/ui/assets/index-DA61Ial2.js +289 -0
- package/dist/ui/favicon.svg +11 -0
- package/dist/ui/fonts/GeistMono-LICENSE.txt +92 -0
- package/dist/ui/fonts/GeistMono-Variable.woff2 +0 -0
- package/dist/ui/index.html +14 -0
- package/dist/{verify-RACBFT2P.js → verify-7DW7LAND.js} +6 -6
- package/dist/{version-HJTVNPOO.js → version-RQLD7VBP.js} +4 -4
- package/package.json +3 -2
- package/skills/myco/SKILL.md +20 -1
- package/skills/myco/references/cli-usage.md +48 -0
- package/skills/myco/references/wisdom.md +11 -1
- package/dist/chunk-2AMAOSRF.js +0 -105
- package/dist/chunk-2AMAOSRF.js.map +0 -1
- package/dist/chunk-5EZ7QF6J.js.map +0 -1
- package/dist/chunk-FIA5NTRH.js.map +0 -1
- package/dist/chunk-FIRMTYFH.js.map +0 -1
- package/dist/chunk-HL2S5QZG.js.map +0 -1
- package/dist/chunk-IURC35BF.js +0 -49
- package/dist/chunk-IURC35BF.js.map +0 -1
- package/dist/chunk-JI6M2L2W.js.map +0 -1
- package/dist/chunk-JJL6AMDA.js.map +0 -1
- package/dist/chunk-KYL67SKZ.js +0 -150
- package/dist/chunk-KYL67SKZ.js.map +0 -1
- package/dist/chunk-TBRZAJ7W.js.map +0 -1
- package/dist/curate-S4HOYWXA.js +0 -231
- package/dist/curate-S4HOYWXA.js.map +0 -1
- package/dist/digest-7NKYXM6G.js.map +0 -1
- package/dist/init-HPQ77WWF.js.map +0 -1
- package/dist/main-NFQ4II75.js.map +0 -1
- package/dist/rebuild-KQ6G2GZM.js +0 -86
- package/dist/rebuild-KQ6G2GZM.js.map +0 -1
- package/dist/setup-digest-DZAFIBEF.js +0 -15
- package/dist/setup-llm-4BZM33YT.js +0 -15
- package/dist/stats-ZIIJ2GB3.js +0 -77
- package/dist/stats-ZIIJ2GB3.js.map +0 -1
- /package/dist/{chunk-ZWUFTOG3.js.map → chunk-2GSX3BK2.js.map} +0 -0
- /package/dist/{chunk-HJG7Z6SJ.js.map → chunk-3EM23DMD.js.map} +0 -0
- /package/dist/{chunk-T7OC6GH5.js.map → chunk-5FNZ7AMX.js.map} +0 -0
- /package/dist/{chunk-X6TKHO22.js.map → chunk-5QWZT4AB.js.map} +0 -0
- /package/dist/{chunk-7KQB22DP.js.map → chunk-E7OBRBCQ.js.map} +0 -0
- /package/dist/{chunk-MIU3DKLN.js.map → chunk-GNR3QAER.js.map} +0 -0
- /package/dist/{chunk-BMJX2IDQ.js.map → chunk-H7PRCVGQ.js.map} +0 -0
- /package/dist/{chunk-6LTNFMXO.js.map → chunk-KC7ENQTN.js.map} +0 -0
- /package/dist/{chunk-ND4VK6C7.js.map → chunk-L25U7PIG.js.map} +0 -0
- /package/dist/{chunk-UKWO26VI.js.map → chunk-YTANWAGE.js.map} +0 -0
- /package/dist/{cli-BLYNNKGJ.js.map → cli-K7SUTP7A.js.map} +0 -0
- /package/dist/{client-5GB4WVXE.js.map → client-YJMNTITQ.js.map} +0 -0
- /package/dist/{config-5FGLQGCW.js.map → config-G5GGT5A6.js.map} +0 -0
- /package/dist/{detect-providers-BIHYFK5M.js.map → detect-providers-S3M5TAMW.js.map} +0 -0
- /package/dist/{logs-BSTBZHDR.js.map → logs-IENORIYR.js.map} +0 -0
- /package/dist/{search-E5JQMTXV.js.map → search-2BVRF54H.js.map} +0 -0
- /package/dist/{session-QF6MILAC.js.map → session-F326AWCH.js.map} +0 -0
- /package/dist/{session-start-5MFEOVQ5.js.map → session-start-AZAF3DTE.js.map} +0 -0
- /package/dist/{setup-digest-DZAFIBEF.js.map → setup-digest-YLZZGSSR.js.map} +0 -0
- /package/dist/{setup-llm-4BZM33YT.js.map → setup-llm-JOXBSLXC.js.map} +0 -0
- /package/dist/{verify-RACBFT2P.js.map → verify-7DW7LAND.js.map} +0 -0
- /package/dist/{version-HJTVNPOO.js.map → version-RQLD7VBP.js.map} +0 -0
|
@@ -2,7 +2,7 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
|
|
|
2
2
|
import {
|
|
3
3
|
MycoConfigSchema,
|
|
4
4
|
require_dist
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MQSYSQ6T.js";
|
|
6
6
|
import {
|
|
7
7
|
__toESM
|
|
8
8
|
} from "./chunk-PZUWP5VK.js";
|
|
@@ -128,8 +128,16 @@ function loadConfig(vaultDir) {
|
|
|
128
128
|
}
|
|
129
129
|
return config;
|
|
130
130
|
}
|
|
131
|
+
function saveConfig(vaultDir, config) {
|
|
132
|
+
const validated = MycoConfigSchema.parse(config);
|
|
133
|
+
const configPath = path2.join(vaultDir, CONFIG_FILENAME);
|
|
134
|
+
fs2.mkdirSync(vaultDir, { recursive: true });
|
|
135
|
+
fs2.writeFileSync(configPath, import_yaml.default.stringify(validated), "utf-8");
|
|
136
|
+
}
|
|
131
137
|
|
|
132
138
|
export {
|
|
133
|
-
|
|
139
|
+
CONFIG_FILENAME,
|
|
140
|
+
loadConfig,
|
|
141
|
+
saveConfig
|
|
134
142
|
};
|
|
135
|
-
//# sourceMappingURL=chunk-
|
|
143
|
+
//# sourceMappingURL=chunk-P3WO3N3I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/loader.ts","../src/config/migrations.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport YAML from 'yaml';\nimport { MycoConfigSchema, type MycoConfig } from './schema.js';\nimport { runMigrations, CURRENT_MIGRATION_VERSION } from './migrations.js';\n\nexport const CONFIG_FILENAME = 'myco.yaml';\n\nexport function loadConfig(vaultDir: string): MycoConfig {\n const configPath = path.join(vaultDir, CONFIG_FILENAME);\n\n if (!fs.existsSync(configPath)) {\n throw new Error(`myco.yaml not found in ${vaultDir}`);\n }\n\n const raw = fs.readFileSync(configPath, 'utf-8');\n const parsed = YAML.parse(raw) as Record<string, unknown>;\n\n // Detect v1 config and guide migration\n if (parsed.version === 1 || (parsed.intelligence as Record<string, unknown>)?.backend) {\n throw new Error(\n 'Myco config uses v1 format. Run /myco:setup-llm to reconfigure for v2.',\n );\n }\n\n // Auto-map legacy 'haiku' provider name to 'anthropic'\n const intel = parsed.intelligence as Record<string, unknown> | undefined;\n const llm = intel?.llm as Record<string, unknown> | undefined;\n if (llm?.provider === 'haiku') {\n llm.provider = 'anthropic';\n }\n\n // Run numbered migrations\n const migrationsRan = runMigrations(parsed, vaultDir, (msg) => {\n // Log to stderr since this runs during config loading (before logger is available)\n process.stderr.write(`[myco migration] ${msg}\\n`);\n });\n\n // Parse with Zod to fill in defaults for new config sections\n const config = MycoConfigSchema.parse(parsed);\n\n // Write back if migrations ran or new defaults were added\n const needsWrite = migrationsRan\n || (parsed.config_version as number ?? 0) < CURRENT_MIGRATION_VERSION\n || !('digest' in parsed);\n\n if (needsWrite) {\n const fullConfig = JSON.parse(JSON.stringify(config)) as Record<string, unknown>;\n fs.writeFileSync(configPath, YAML.stringify(fullConfig), 'utf-8');\n }\n\n return config;\n}\n\nexport function saveConfig(vaultDir: string, config: MycoConfig): void {\n // Validate before writing — OAK lesson: validate on write, not just read\n const validated = MycoConfigSchema.parse(config);\n\n const configPath = path.join(vaultDir, CONFIG_FILENAME);\n fs.mkdirSync(vaultDir, { recursive: true });\n fs.writeFileSync(configPath, YAML.stringify(validated), 'utf-8');\n}\n","/**\n * Config and vault migrations — run once per version, tracked by config_version.\n *\n * Each migration has a version number, a name, and a function that receives\n * the raw parsed YAML doc and the vault directory. Migrations run in order\n * and are skipped if config_version is already past them.\n *\n * To add a new migration:\n * 1. Add an entry to MIGRATIONS with the next version number\n * 2. Write the migrate function — it receives the mutable doc and vaultDir\n * 3. The framework handles version tracking and writing the config back\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface Migration {\n version: number;\n name: string;\n migrate: (doc: Record<string, unknown>, vaultDir: string) => void;\n}\n\n/** Regex matching both quoted and unquoted YAML: type: memory, type: \"memory\", type: 'memory' */\nconst MEMORY_TYPE_PATTERN = /type:\\s*[\"']?memory[\"']?/g;\n\nexport const MIGRATIONS: Migration[] = [\n {\n version: 1,\n name: 'rename-memories-to-spores',\n migrate: (doc, vaultDir) => {\n // Config: rename context.layers.memories → context.layers.spores\n const context = doc.context as Record<string, unknown> | undefined;\n const layers = context?.layers as Record<string, unknown> | undefined;\n if (layers && 'memories' in layers && !('spores' in layers)) {\n layers.spores = layers.memories;\n delete layers.memories;\n }\n\n // Vault: rename memories/ directory → spores/\n const memoriesDir = path.join(vaultDir, 'memories');\n const sporesDir = path.join(vaultDir, 'spores');\n\n if (!fs.existsSync(memoriesDir)) return;\n\n if (fs.existsSync(sporesDir)) {\n // Both exist (interrupted migration) — merge remaining files\n const moveRemaining = (srcDir: string, destDir: string): void => {\n for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {\n const srcPath = path.join(srcDir, entry.name);\n const destPath = path.join(destDir, entry.name);\n if (entry.isDirectory()) {\n if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });\n moveRemaining(srcPath, destPath);\n } else if (!fs.existsSync(destPath)) {\n fs.renameSync(srcPath, destPath);\n }\n }\n };\n moveRemaining(memoriesDir, sporesDir);\n fs.rmSync(memoriesDir, { recursive: true, force: true });\n } else {\n fs.renameSync(memoriesDir, sporesDir);\n }\n\n // Update frontmatter type: memory → type: spore (handles quoted and unquoted)\n const walkUpdate = (dir: string): void => {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) { walkUpdate(fullPath); continue; }\n if (!entry.name.endsWith('.md')) continue;\n const content = fs.readFileSync(fullPath, 'utf-8');\n MEMORY_TYPE_PATTERN.lastIndex = 0;\n if (MEMORY_TYPE_PATTERN.test(content)) {\n MEMORY_TYPE_PATTERN.lastIndex = 0;\n fs.writeFileSync(fullPath, content.replace(MEMORY_TYPE_PATTERN, 'type: spore'));\n }\n }\n };\n walkUpdate(sporesDir);\n\n // Update wikilinks in ALL vault files: [[memories/...]] → [[spores/...]]\n const walkLinks = (dir: string): void => {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) { walkLinks(fullPath); continue; }\n if (!entry.name.endsWith('.md')) continue;\n const content = fs.readFileSync(fullPath, 'utf-8');\n if (content.includes('memories/')) {\n fs.writeFileSync(fullPath, content.replace(/memories\\//g, 'spores/'));\n }\n }\n };\n walkLinks(vaultDir);\n },\n },\n];\n\n/** Current migration version — the highest version in MIGRATIONS. */\nexport const CURRENT_MIGRATION_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;\n\n/**\n * Run all pending migrations on the raw config doc.\n * Returns true if any migrations ran (caller should reindex).\n */\nexport function runMigrations(\n doc: Record<string, unknown>,\n vaultDir: string,\n log?: (message: string) => void,\n): boolean {\n const currentVersion = (doc.config_version as number) ?? 0;\n let ran = false;\n\n for (const migration of MIGRATIONS) {\n if (migration.version <= currentVersion) continue;\n\n log?.(`Running migration ${migration.version}: ${migration.name}`);\n migration.migrate(doc, vaultDir);\n doc.config_version = migration.version;\n ran = true;\n }\n\n return ran;\n}\n"],"mappings":"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","names":["fs","path","path","fs","YAML"]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
DAEMON_CLIENT_TIMEOUT_MS,
|
|
4
4
|
EMBEDDING_REQUEST_TIMEOUT_MS,
|
|
5
5
|
LLM_REQUEST_TIMEOUT_MS
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-6BSDCZ5Q.js";
|
|
7
7
|
|
|
8
8
|
// src/intelligence/ollama.ts
|
|
9
9
|
var ENDPOINT_GENERATE = "/api/generate";
|
|
@@ -15,16 +15,19 @@ var OllamaBackend = class _OllamaBackend {
|
|
|
15
15
|
baseUrl;
|
|
16
16
|
model;
|
|
17
17
|
defaultMaxTokens;
|
|
18
|
+
contextWindow;
|
|
18
19
|
constructor(config) {
|
|
19
20
|
this.baseUrl = config?.base_url ?? _OllamaBackend.DEFAULT_BASE_URL;
|
|
20
21
|
this.model = config?.model ?? config?.summary_model ?? "llama3.2";
|
|
21
22
|
this.defaultMaxTokens = config?.max_tokens ?? 1024;
|
|
23
|
+
this.contextWindow = config?.context_window;
|
|
22
24
|
}
|
|
23
25
|
async summarize(prompt, opts) {
|
|
24
26
|
const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;
|
|
27
|
+
const contextLength = opts?.contextLength ?? this.contextWindow;
|
|
25
28
|
const options = { num_predict: maxTokens };
|
|
26
|
-
if (
|
|
27
|
-
options.num_ctx =
|
|
29
|
+
if (contextLength) {
|
|
30
|
+
options.num_ctx = contextLength;
|
|
28
31
|
}
|
|
29
32
|
const body = {
|
|
30
33
|
model: this.model,
|
|
@@ -270,4 +273,4 @@ export {
|
|
|
270
273
|
OllamaBackend,
|
|
271
274
|
LmStudioBackend
|
|
272
275
|
};
|
|
273
|
-
//# sourceMappingURL=chunk-
|
|
276
|
+
//# sourceMappingURL=chunk-QGJ2ZIUZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/intelligence/ollama.ts","../src/intelligence/lm-studio.ts"],"sourcesContent":["import type { LlmProvider, EmbeddingProvider, LlmResponse, EmbeddingResponse, LlmRequestOptions } from './llm.js';\nimport { LLM_REQUEST_TIMEOUT_MS, EMBEDDING_REQUEST_TIMEOUT_MS, DAEMON_CLIENT_TIMEOUT_MS } from '../constants.js';\n\ninterface OllamaConfig {\n model?: string;\n base_url?: string;\n context_window?: number;\n max_tokens?: number;\n // Legacy fields (ignored, kept for backward compat during migration)\n embedding_model?: string;\n summary_model?: string;\n}\n\n// Ollama API endpoints\nconst ENDPOINT_GENERATE = '/api/generate';\nconst ENDPOINT_EMBED = '/api/embed';\nconst ENDPOINT_TAGS = '/api/tags';\n\nexport class OllamaBackend implements LlmProvider, EmbeddingProvider {\n static readonly DEFAULT_BASE_URL = 'http://localhost:11434';\n readonly name = 'ollama';\n private baseUrl: string;\n private model: string;\n private defaultMaxTokens: number;\n private contextWindow: number | undefined;\n\n constructor(config?: OllamaConfig) {\n this.baseUrl = config?.base_url ?? OllamaBackend.DEFAULT_BASE_URL;\n this.model = config?.model ?? config?.summary_model ?? 'llama3.2';\n this.defaultMaxTokens = config?.max_tokens ?? 1024;\n this.contextWindow = config?.context_window;\n }\n\n async summarize(prompt: string, opts?: LlmRequestOptions): Promise<LlmResponse> {\n const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;\n\n // Send num_ctx from config or per-call override. Ollama reloads the model\n // on num_ctx changes, but consistent values (same num_ctx every call)\n // only cause one reload on first use. Without this, Ollama falls back to\n // its model default (often 2048), ignoring the user's configured context.\n const contextLength = opts?.contextLength ?? this.contextWindow;\n const options: Record<string, unknown> = { num_predict: maxTokens };\n if (contextLength) {\n options.num_ctx = contextLength;\n }\n\n const body: Record<string, unknown> = {\n model: this.model,\n prompt,\n stream: false,\n options,\n };\n\n // System prompt — sent as a separate field instead of concatenated into prompt\n if (opts?.systemPrompt) {\n body.system = opts.systemPrompt;\n }\n\n // Thinking control — false suppresses chain-of-thought for reasoning models\n if (opts?.reasoning) {\n body.think = opts.reasoning === 'off' ? false : opts.reasoning;\n }\n\n // Keep model loaded between requests (useful for digest cycles)\n if (opts?.keepAlive) {\n body.keep_alive = opts.keepAlive;\n }\n\n const response = await fetch(`${this.baseUrl}${ENDPOINT_GENERATE}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(opts?.timeoutMs ?? LLM_REQUEST_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => '');\n throw new Error(`Ollama summarize failed: ${response.status} ${errorBody.slice(0, 500)}`);\n }\n\n const data = await response.json() as { response: string; model: string };\n return { text: data.response, model: data.model };\n }\n\n async embed(text: string): Promise<EmbeddingResponse> {\n const response = await fetch(`${this.baseUrl}${ENDPOINT_EMBED}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.model,\n input: text,\n }),\n signal: AbortSignal.timeout(EMBEDDING_REQUEST_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n throw new Error(`Ollama embed failed: ${response.status} ${response.statusText}`);\n }\n\n const data = await response.json() as { embeddings: number[][]; model: string };\n const embedding = data.embeddings[0];\n return { embedding, model: data.model, dimensions: embedding.length };\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}${ENDPOINT_TAGS}`, {\n signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /** List available models on this Ollama instance. */\n async listModels(timeoutMs?: number): Promise<string[]> {\n try {\n const response = await fetch(`${this.baseUrl}${ENDPOINT_TAGS}`, {\n signal: AbortSignal.timeout(timeoutMs ?? DAEMON_CLIENT_TIMEOUT_MS),\n });\n const data = await response.json() as { models: Array<{ name: string }> };\n return data.models.map((m) => m.name);\n } catch {\n return [];\n }\n }\n}\n","import type { LlmProvider, EmbeddingProvider, LlmResponse, EmbeddingResponse, LlmRequestOptions } from './llm.js';\nimport { LLM_REQUEST_TIMEOUT_MS, EMBEDDING_REQUEST_TIMEOUT_MS, DAEMON_CLIENT_TIMEOUT_MS } from '../constants.js';\n\ninterface LmStudioConfig {\n model?: string;\n base_url?: string;\n context_window?: number;\n max_tokens?: number;\n // Legacy fields\n embedding_model?: string;\n summary_model?: string;\n}\n\n// LM Studio API endpoints\nconst ENDPOINT_CHAT = '/api/v1/chat';\nconst ENDPOINT_MODELS_LOAD = '/api/v1/models/load';\nconst ENDPOINT_MODELS_LIST = '/v1/models';\nconst ENDPOINT_MODELS_NATIVE = '/api/v1/models';\nconst ENDPOINT_EMBEDDINGS = '/v1/embeddings';\n\n/** Shape of a loaded instance from the LM Studio native models API. */\ninterface NativeLoadedInstance {\n id: string;\n config: {\n context_length: number;\n flash_attention: boolean;\n offload_kv_cache_to_gpu: boolean;\n };\n}\n\n/** Shape of a model entry from the LM Studio native models API. */\ninterface NativeModelEntry {\n type: string;\n key: string;\n loaded_instances: NativeLoadedInstance[];\n}\n\nexport class LmStudioBackend implements LlmProvider, EmbeddingProvider {\n static readonly DEFAULT_BASE_URL = 'http://localhost:1234';\n readonly name = 'lm-studio';\n private baseUrl: string;\n private model: string;\n private instanceId: string | null = null;\n private contextWindow: number | undefined;\n private defaultMaxTokens: number;\n\n constructor(config?: LmStudioConfig) {\n this.baseUrl = config?.base_url ?? LmStudioBackend.DEFAULT_BASE_URL;\n this.model = config?.model ?? config?.summary_model ?? 'llama3.2';\n this.contextWindow = config?.context_window;\n this.defaultMaxTokens = config?.max_tokens ?? 1024;\n }\n\n /**\n * Generate text using LM Studio's native REST API (/api/v1/chat).\n * Routes to our specific instance by ID when available, with model name +\n * context_length as fallback. This ensures correct routing when multiple\n * daemons share the same LM Studio, and graceful degradation when our\n * instance is evicted by idle TTL.\n */\n async summarize(prompt: string, opts?: LlmRequestOptions): Promise<LlmResponse> {\n const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;\n const contextLength = opts?.contextLength ?? this.contextWindow;\n\n const body: Record<string, unknown> = {\n model: this.instanceId ?? this.model,\n input: prompt,\n max_output_tokens: maxTokens,\n store: false,\n };\n\n // Always send context_length — even when routing by instance ID.\n // If our instance was evicted and LM Studio auto-loads, this ensures\n // the replacement gets the correct context window.\n if (contextLength) {\n body.context_length = contextLength;\n }\n\n // System prompt — sent separately from user content\n if (opts?.systemPrompt) {\n body.system_prompt = opts.systemPrompt;\n }\n\n // Reasoning control — 'off' suppresses chain-of-thought for reasoning models\n if (opts?.reasoning) {\n body.reasoning = opts.reasoning;\n }\n\n const response = await fetch(`${this.baseUrl}${ENDPOINT_CHAT}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(opts?.timeoutMs ?? LLM_REQUEST_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => '');\n // If our instance was evicted, clear the ID so ensureLoaded\n // reloads on the next cycle instead of hitting a stale ID repeatedly\n if (response.status === 404 && this.instanceId) {\n this.instanceId = null;\n }\n throw new Error(`LM Studio summarize failed: ${response.status} ${errorBody.slice(0, 500)}`);\n }\n\n const data = await response.json() as {\n model_instance_id: string;\n output: Array<{ type: string; content: string }>;\n };\n const messageOutput = data.output.find((o) => o.type === 'message');\n const text = messageOutput?.content ?? '';\n return { text, model: data.model_instance_id };\n }\n\n /**\n * Generate embeddings using LM Studio's OpenAI-compatible endpoint.\n * (The native API doesn't have an embedding endpoint — OpenAI-compat is fine here.)\n */\n async embed(text: string): Promise<EmbeddingResponse> {\n const response = await fetch(`${this.baseUrl}${ENDPOINT_EMBEDDINGS}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n model: this.model,\n input: text,\n }),\n signal: AbortSignal.timeout(EMBEDDING_REQUEST_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n throw new Error(`LM Studio embed failed: ${response.status}`);\n }\n\n const data = await response.json() as {\n data: Array<{ embedding: number[] }>;\n model: string;\n };\n const embedding = data.data[0].embedding;\n return { embedding, model: data.model, dimensions: embedding.length };\n }\n\n /**\n * Ensure a model instance is loaded with the desired settings.\n * Called every digest cycle (not cached) so it recovers from idle TTL eviction.\n *\n * The load API is necessary to control offload_kv_cache_to_gpu — a load-time\n * setting that cannot be set per-request via the chat API.\n *\n * Multi-daemon safe: finds or loads our own compatible instance without\n * touching instances from other daemons/projects. Routes by instance ID.\n */\n async ensureLoaded(contextLength?: number, gpuKvCache?: boolean): Promise<void> {\n const ctx = contextLength ?? this.contextWindow;\n const kvCache = gpuKvCache ?? false;\n\n // Query native API for existing loaded instances of this model\n const instances = await this.getLoadedInstances();\n\n // Look for a compatible instance we can reuse (ours or anyone's)\n for (const instance of instances) {\n const matchesContext = !ctx || instance.config.context_length === ctx;\n const matchesKvCache = instance.config.offload_kv_cache_to_gpu === kvCache;\n if (matchesContext && matchesKvCache) {\n this.instanceId = instance.id;\n return;\n }\n }\n\n // No compatible instance — load our own (don't touch others)\n const body: Record<string, unknown> = {\n model: this.model,\n flash_attention: true,\n offload_kv_cache_to_gpu: kvCache,\n };\n if (ctx) {\n body.context_length = ctx;\n }\n\n const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_LOAD}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(LLM_REQUEST_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => '');\n throw new Error(`LM Studio model load failed: ${response.status} ${errorBody.slice(0, 200)}`);\n }\n\n const loadResult = await response.json() as Record<string, unknown>;\n const id = (loadResult.instance_id ?? loadResult.id ?? loadResult.model_instance_id) as string | undefined;\n if (id) {\n this.instanceId = id;\n }\n }\n\n /**\n * Query the LM Studio native API for loaded instances of this model.\n * Returns an empty array if the API is unavailable or the model has no loaded instances.\n */\n private async getLoadedInstances(): Promise<NativeLoadedInstance[]> {\n try {\n const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_NATIVE}`, {\n signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS),\n });\n if (!response.ok) return [];\n\n const data = await response.json() as { models: NativeModelEntry[] };\n const entry = data.models.find((m) => m.key === this.model);\n return entry?.loaded_instances ?? [];\n } catch {\n return [];\n }\n }\n\n async isAvailable(): Promise<boolean> {\n try {\n const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_LIST}`, {\n signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /** List available models on this LM Studio instance. */\n async listModels(timeoutMs?: number): Promise<string[]> {\n try {\n const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_LIST}`, {\n signal: AbortSignal.timeout(timeoutMs ?? DAEMON_CLIENT_TIMEOUT_MS),\n });\n const data = await response.json() as { data: Array<{ id: string }> };\n return data.data.map((m) => m.id);\n } catch {\n return [];\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,6 +3,65 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
|
|
|
3
3
|
// src/daemon/logger.ts
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
|
+
|
|
7
|
+
// src/daemon/log-buffer.ts
|
|
8
|
+
var LOG_RING_BUFFER_CAPACITY = 1e3;
|
|
9
|
+
var LOG_QUERY_DEFAULT_LIMIT = 100;
|
|
10
|
+
var LogRingBuffer = class {
|
|
11
|
+
buffer;
|
|
12
|
+
head = 0;
|
|
13
|
+
count = 0;
|
|
14
|
+
sequence = 0;
|
|
15
|
+
startSequence = 0;
|
|
16
|
+
capacity;
|
|
17
|
+
constructor(capacity = LOG_RING_BUFFER_CAPACITY) {
|
|
18
|
+
this.capacity = capacity;
|
|
19
|
+
this.buffer = new Array(capacity);
|
|
20
|
+
}
|
|
21
|
+
push(entry) {
|
|
22
|
+
this.buffer[this.head] = entry;
|
|
23
|
+
this.head = (this.head + 1) % this.capacity;
|
|
24
|
+
if (this.count < this.capacity) {
|
|
25
|
+
this.count++;
|
|
26
|
+
} else {
|
|
27
|
+
this.startSequence++;
|
|
28
|
+
}
|
|
29
|
+
this.sequence++;
|
|
30
|
+
}
|
|
31
|
+
since(cursor, options) {
|
|
32
|
+
const limit = options?.limit ?? LOG_QUERY_DEFAULT_LIMIT;
|
|
33
|
+
const minLevel = options?.level ? LEVEL_ORDER[options.level] : 0;
|
|
34
|
+
let startIdx = 0;
|
|
35
|
+
let cursorReset = false;
|
|
36
|
+
if (cursor !== null) {
|
|
37
|
+
const seq = parseInt(cursor, 10);
|
|
38
|
+
if (isNaN(seq) || seq < this.startSequence) {
|
|
39
|
+
cursorReset = true;
|
|
40
|
+
startIdx = 0;
|
|
41
|
+
} else {
|
|
42
|
+
startIdx = seq - this.startSequence;
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
startIdx = Math.max(0, this.count - limit);
|
|
46
|
+
}
|
|
47
|
+
const entries = [];
|
|
48
|
+
for (let i = startIdx; i < this.count && entries.length < limit; i++) {
|
|
49
|
+
const bufIdx = (this.head - this.count + i + this.capacity) % this.capacity;
|
|
50
|
+
const entry = this.buffer[bufIdx];
|
|
51
|
+
if (entry && LEVEL_ORDER[entry.level] >= minLevel) {
|
|
52
|
+
entries.push(entry);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const result = {
|
|
56
|
+
entries,
|
|
57
|
+
cursor: String(this.sequence)
|
|
58
|
+
};
|
|
59
|
+
if (cursorReset) result.cursor_reset = true;
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/daemon/logger.ts
|
|
6
65
|
var LEVEL_ORDER = {
|
|
7
66
|
debug: 0,
|
|
8
67
|
info: 1,
|
|
@@ -17,12 +76,14 @@ var DaemonLogger = class {
|
|
|
17
76
|
maxSize;
|
|
18
77
|
maxFiles;
|
|
19
78
|
logDir;
|
|
79
|
+
ringBuffer;
|
|
20
80
|
constructor(logDir, options = {}) {
|
|
21
81
|
this.logDir = logDir;
|
|
22
82
|
this.logPath = path.join(logDir, "daemon.log");
|
|
23
83
|
this.level = options.level ?? "info";
|
|
24
84
|
this.maxSize = options.maxSize ?? 5242880;
|
|
25
85
|
this.maxFiles = options.maxFiles ?? 3;
|
|
86
|
+
this.ringBuffer = new LogRingBuffer();
|
|
26
87
|
fs.mkdirSync(logDir, { recursive: true });
|
|
27
88
|
this.fd = fs.openSync(this.logPath, "a");
|
|
28
89
|
try {
|
|
@@ -49,6 +110,9 @@ var DaemonLogger = class {
|
|
|
49
110
|
this.fd = null;
|
|
50
111
|
}
|
|
51
112
|
}
|
|
113
|
+
getRingBuffer() {
|
|
114
|
+
return this.ringBuffer;
|
|
115
|
+
}
|
|
52
116
|
write(level, component, message, data) {
|
|
53
117
|
if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) return;
|
|
54
118
|
const entry = {
|
|
@@ -58,6 +122,7 @@ var DaemonLogger = class {
|
|
|
58
122
|
message,
|
|
59
123
|
...data
|
|
60
124
|
};
|
|
125
|
+
this.ringBuffer.push(entry);
|
|
61
126
|
const line = JSON.stringify(entry) + "\n";
|
|
62
127
|
const bytes = Buffer.byteLength(line);
|
|
63
128
|
if (this.currentSize + bytes > this.maxSize) {
|
|
@@ -93,4 +158,4 @@ export {
|
|
|
93
158
|
LEVEL_ORDER,
|
|
94
159
|
DaemonLogger
|
|
95
160
|
};
|
|
96
|
-
//# sourceMappingURL=chunk-
|
|
161
|
+
//# sourceMappingURL=chunk-QLUE3BUL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/logger.ts","../src/daemon/log-buffer.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { LogRingBuffer } from './log-buffer.js';\n\nexport interface LogEntry {\n timestamp: string;\n level: string;\n component: string;\n message: string;\n [key: string]: unknown;\n}\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport const LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0, info: 1, warn: 2, error: 3,\n};\n\ninterface LoggerOptions {\n level?: LogLevel;\n maxSize?: number;\n maxFiles?: number;\n}\n\nexport class DaemonLogger {\n private logPath: string;\n private fd: number | null = null;\n private currentSize = 0;\n private level: LogLevel;\n private maxSize: number;\n private maxFiles: number;\n private logDir: string;\n private ringBuffer: LogRingBuffer;\n\n constructor(logDir: string, options: LoggerOptions = {}) {\n this.logDir = logDir;\n this.logPath = path.join(logDir, 'daemon.log');\n this.level = options.level ?? 'info';\n this.maxSize = options.maxSize ?? 5_242_880;\n this.maxFiles = options.maxFiles ?? 3;\n this.ringBuffer = new LogRingBuffer();\n\n fs.mkdirSync(logDir, { recursive: true });\n this.fd = fs.openSync(this.logPath, 'a');\n try {\n this.currentSize = fs.fstatSync(this.fd).size;\n } catch {\n this.currentSize = 0;\n }\n }\n\n debug(component: string, message: string, data?: Record<string, unknown>): void {\n this.write('debug', component, message, data);\n }\n\n info(component: string, message: string, data?: Record<string, unknown>): void {\n this.write('info', component, message, data);\n }\n\n warn(component: string, message: string, data?: Record<string, unknown>): void {\n this.write('warn', component, message, data);\n }\n\n error(component: string, message: string, data?: Record<string, unknown>): void {\n this.write('error', component, message, data);\n }\n\n close(): void {\n if (this.fd !== null) {\n fs.closeSync(this.fd);\n this.fd = null;\n }\n }\n\n getRingBuffer(): LogRingBuffer {\n return this.ringBuffer;\n }\n\n private write(level: LogLevel, component: string, message: string, data?: Record<string, unknown>): void {\n if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) return;\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n component,\n message,\n ...data,\n };\n\n this.ringBuffer.push(entry);\n\n const line = JSON.stringify(entry) + '\\n';\n const bytes = Buffer.byteLength(line);\n\n if (this.currentSize + bytes > this.maxSize) {\n this.rotate();\n }\n\n if (this.fd !== null) {\n fs.writeSync(this.fd, line);\n this.currentSize += bytes;\n }\n }\n\n private rotate(): void {\n this.close();\n\n for (let i = this.maxFiles - 1; i >= 1; i--) {\n const from = path.join(this.logDir, `daemon.${i}.log`);\n const to = path.join(this.logDir, `daemon.${i + 1}.log`);\n if (fs.existsSync(from)) {\n if (i + 1 > this.maxFiles) {\n fs.unlinkSync(from);\n } else {\n fs.renameSync(from, to);\n }\n }\n }\n\n if (fs.existsSync(this.logPath)) {\n fs.renameSync(this.logPath, path.join(this.logDir, 'daemon.1.log'));\n }\n\n this.fd = fs.openSync(this.logPath, 'a');\n this.currentSize = 0;\n }\n}\n","import type { LogEntry, LogLevel } from './logger.js';\nimport { LEVEL_ORDER } from './logger.js';\n\nconst LOG_RING_BUFFER_CAPACITY = 1000;\nconst LOG_QUERY_DEFAULT_LIMIT = 100;\n\ninterface LogQueryResult {\n entries: LogEntry[];\n cursor: string;\n cursor_reset?: boolean;\n}\n\ninterface LogQueryOptions {\n level?: LogLevel;\n limit?: number;\n}\n\nexport class LogRingBuffer {\n private buffer: LogEntry[];\n private head = 0;\n private count = 0;\n private sequence = 0;\n private startSequence = 0;\n private readonly capacity: number;\n\n constructor(capacity = LOG_RING_BUFFER_CAPACITY) {\n this.capacity = capacity;\n this.buffer = new Array(capacity);\n }\n\n push(entry: LogEntry): void {\n this.buffer[this.head] = entry;\n this.head = (this.head + 1) % this.capacity;\n if (this.count < this.capacity) {\n this.count++;\n } else {\n this.startSequence++;\n }\n this.sequence++;\n }\n\n since(cursor: string | null, options?: LogQueryOptions): LogQueryResult {\n const limit = options?.limit ?? LOG_QUERY_DEFAULT_LIMIT;\n const minLevel = options?.level ? LEVEL_ORDER[options.level] : 0;\n\n let startIdx = 0;\n let cursorReset = false;\n\n if (cursor !== null) {\n const seq = parseInt(cursor, 10);\n if (isNaN(seq) || seq < this.startSequence) {\n cursorReset = true;\n startIdx = 0;\n } else {\n startIdx = seq - this.startSequence;\n }\n } else {\n // No cursor: return last `limit` entries\n startIdx = Math.max(0, this.count - limit);\n }\n\n const entries: LogEntry[] = [];\n for (let i = startIdx; i < this.count && entries.length < limit; i++) {\n const bufIdx = (this.head - this.count + i + this.capacity) % this.capacity;\n const entry = this.buffer[bufIdx];\n if (entry && LEVEL_ORDER[entry.level as LogLevel] >= minLevel) {\n entries.push(entry);\n }\n }\n\n const result: LogQueryResult = {\n entries,\n cursor: String(this.sequence),\n };\n if (cursorReset) result.cursor_reset = true;\n return result;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -105,6 +105,22 @@ var MycoIndex = class {
|
|
|
105
105
|
const rows = this.db.prepare(sql).all(...ids);
|
|
106
106
|
return rows.map((row) => ({ ...row, frontmatter: JSON.parse(row.frontmatter) }));
|
|
107
107
|
}
|
|
108
|
+
/** Count notes by type using SQL aggregation (much faster than materializing all rows). */
|
|
109
|
+
countByType() {
|
|
110
|
+
const rows = this.db.prepare("SELECT type, COUNT(*) as count FROM notes GROUP BY type").all();
|
|
111
|
+
const result = {};
|
|
112
|
+
for (const row of rows) result[row.type] = row.count;
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
/** Count spores grouped by observation_type. */
|
|
116
|
+
sporeCountsByObservationType() {
|
|
117
|
+
const rows = this.db.prepare(
|
|
118
|
+
"SELECT json_extract(frontmatter, '$.observation_type') as obs_type, COUNT(*) as count FROM notes WHERE type = 'spore' GROUP BY obs_type"
|
|
119
|
+
).all();
|
|
120
|
+
const result = {};
|
|
121
|
+
for (const row of rows) result[row.obs_type ?? "unknown"] = row.count;
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
108
124
|
close() {
|
|
109
125
|
this.db.close();
|
|
110
126
|
}
|
|
@@ -113,4 +129,4 @@ var MycoIndex = class {
|
|
|
113
129
|
export {
|
|
114
130
|
MycoIndex
|
|
115
131
|
};
|
|
116
|
-
//# sourceMappingURL=chunk-
|
|
132
|
+
//# sourceMappingURL=chunk-TWSTAVLO.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index/sqlite.ts"],"sourcesContent":["import Database from 'better-sqlite3';\n\nexport interface IndexedNote {\n path: string;\n type: string;\n id: string;\n title: string;\n content: string;\n frontmatter: Record<string, unknown>;\n created: string;\n updated_at?: string;\n}\n\nexport interface QueryOptions {\n type?: string;\n id?: string;\n limit?: number;\n since?: string;\n /** Filter by updated_at — returns notes with updated_at >= this ISO string. */\n updatedSince?: string;\n /** Filter by frontmatter fields using json_extract. Applied before LIMIT. */\n frontmatter?: Record<string, string>;\n}\n\nexport class MycoIndex {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.init();\n }\n\n private init(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS notes (\n path TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n id TEXT NOT NULL,\n title TEXT NOT NULL DEFAULT '',\n content TEXT NOT NULL DEFAULT '',\n frontmatter TEXT NOT NULL DEFAULT '{}',\n created TEXT NOT NULL,\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE INDEX IF NOT EXISTS idx_notes_type ON notes(type);\n CREATE INDEX IF NOT EXISTS idx_notes_id ON notes(id);\n CREATE INDEX IF NOT EXISTS idx_notes_created ON notes(created);\n CREATE INDEX IF NOT EXISTS idx_notes_updated_at ON notes(updated_at);\n `);\n }\n\n getPragma(name: string): unknown {\n return this.db.pragma(`${name}`, { simple: true });\n }\n\n getDb(): Database.Database {\n return this.db;\n }\n\n upsertNote(note: Omit<IndexedNote, 'updated_at'>): void {\n const stmt = this.db.prepare(`\n INSERT INTO notes (path, type, id, title, content, frontmatter, created, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))\n ON CONFLICT(path) DO UPDATE SET\n type = excluded.type,\n id = excluded.id,\n title = excluded.title,\n content = excluded.content,\n frontmatter = excluded.frontmatter,\n created = excluded.created,\n updated_at = datetime('now')\n `);\n stmt.run(\n note.path,\n note.type,\n note.id,\n note.title,\n note.content,\n JSON.stringify(note.frontmatter),\n note.created,\n );\n }\n\n getNoteByPath(notePath: string): IndexedNote | null {\n const row = this.db.prepare('SELECT * FROM notes WHERE path = ?').get(notePath) as any;\n if (!row) return null;\n return { ...row, frontmatter: JSON.parse(row.frontmatter) };\n }\n\n deleteNote(notePath: string): void {\n this.db.prepare('DELETE FROM notes WHERE path = ?').run(notePath);\n }\n\n query(options: QueryOptions): IndexedNote[] {\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (options.type) {\n conditions.push('type = ?');\n params.push(options.type);\n }\n if (options.id) {\n conditions.push('id = ?');\n params.push(options.id);\n }\n if (options.since) {\n conditions.push('created >= ?');\n params.push(options.since);\n }\n if (options.updatedSince) {\n // Use SQLite datetime() to handle ISO 8601 formats (Z-suffix, offsets, milliseconds)\n conditions.push('updated_at >= datetime(?)');\n params.push(options.updatedSince);\n }\n if (options.frontmatter) {\n for (const [key, value] of Object.entries(options.frontmatter)) {\n conditions.push(`json_extract(frontmatter, '$.' || ?) = ?`);\n params.push(key, value);\n }\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n const limitClause = options.limit ? 'LIMIT ?' : '';\n if (options.limit) params.push(options.limit);\n const sql = `SELECT * FROM notes ${where} ORDER BY created DESC ${limitClause}`;\n\n const rows = this.db.prepare(sql).all(...params) as any[];\n return rows.map((row) => ({ ...row, frontmatter: JSON.parse(row.frontmatter) }));\n }\n\n queryByIds(ids: string[]): IndexedNote[] {\n if (ids.length === 0) return [];\n const placeholders = ids.map(() => '?').join(',');\n const sql = `SELECT * FROM notes WHERE id IN (${placeholders})`;\n const rows = this.db.prepare(sql).all(...ids) as any[];\n return rows.map((row) => ({ ...row, frontmatter: JSON.parse(row.frontmatter) }));\n }\n\n close(): void {\n this.db.close();\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index/sqlite.ts"],"sourcesContent":["import Database from 'better-sqlite3';\n\nexport interface IndexedNote {\n path: string;\n type: string;\n id: string;\n title: string;\n content: string;\n frontmatter: Record<string, unknown>;\n created: string;\n updated_at?: string;\n}\n\nexport interface QueryOptions {\n type?: string;\n id?: string;\n limit?: number;\n since?: string;\n /** Filter by updated_at — returns notes with updated_at >= this ISO string. */\n updatedSince?: string;\n /** Filter by frontmatter fields using json_extract. Applied before LIMIT. */\n frontmatter?: Record<string, string>;\n}\n\nexport class MycoIndex {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.init();\n }\n\n private init(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS notes (\n path TEXT PRIMARY KEY,\n type TEXT NOT NULL,\n id TEXT NOT NULL,\n title TEXT NOT NULL DEFAULT '',\n content TEXT NOT NULL DEFAULT '',\n frontmatter TEXT NOT NULL DEFAULT '{}',\n created TEXT NOT NULL,\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n );\n\n CREATE INDEX IF NOT EXISTS idx_notes_type ON notes(type);\n CREATE INDEX IF NOT EXISTS idx_notes_id ON notes(id);\n CREATE INDEX IF NOT EXISTS idx_notes_created ON notes(created);\n CREATE INDEX IF NOT EXISTS idx_notes_updated_at ON notes(updated_at);\n `);\n }\n\n getPragma(name: string): unknown {\n return this.db.pragma(`${name}`, { simple: true });\n }\n\n getDb(): Database.Database {\n return this.db;\n }\n\n upsertNote(note: Omit<IndexedNote, 'updated_at'>): void {\n const stmt = this.db.prepare(`\n INSERT INTO notes (path, type, id, title, content, frontmatter, created, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))\n ON CONFLICT(path) DO UPDATE SET\n type = excluded.type,\n id = excluded.id,\n title = excluded.title,\n content = excluded.content,\n frontmatter = excluded.frontmatter,\n created = excluded.created,\n updated_at = datetime('now')\n `);\n stmt.run(\n note.path,\n note.type,\n note.id,\n note.title,\n note.content,\n JSON.stringify(note.frontmatter),\n note.created,\n );\n }\n\n getNoteByPath(notePath: string): IndexedNote | null {\n const row = this.db.prepare('SELECT * FROM notes WHERE path = ?').get(notePath) as any;\n if (!row) return null;\n return { ...row, frontmatter: JSON.parse(row.frontmatter) };\n }\n\n deleteNote(notePath: string): void {\n this.db.prepare('DELETE FROM notes WHERE path = ?').run(notePath);\n }\n\n query(options: QueryOptions): IndexedNote[] {\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (options.type) {\n conditions.push('type = ?');\n params.push(options.type);\n }\n if (options.id) {\n conditions.push('id = ?');\n params.push(options.id);\n }\n if (options.since) {\n conditions.push('created >= ?');\n params.push(options.since);\n }\n if (options.updatedSince) {\n // Use SQLite datetime() to handle ISO 8601 formats (Z-suffix, offsets, milliseconds)\n conditions.push('updated_at >= datetime(?)');\n params.push(options.updatedSince);\n }\n if (options.frontmatter) {\n for (const [key, value] of Object.entries(options.frontmatter)) {\n conditions.push(`json_extract(frontmatter, '$.' || ?) = ?`);\n params.push(key, value);\n }\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n const limitClause = options.limit ? 'LIMIT ?' : '';\n if (options.limit) params.push(options.limit);\n const sql = `SELECT * FROM notes ${where} ORDER BY created DESC ${limitClause}`;\n\n const rows = this.db.prepare(sql).all(...params) as any[];\n return rows.map((row) => ({ ...row, frontmatter: JSON.parse(row.frontmatter) }));\n }\n\n queryByIds(ids: string[]): IndexedNote[] {\n if (ids.length === 0) return [];\n const placeholders = ids.map(() => '?').join(',');\n const sql = `SELECT * FROM notes WHERE id IN (${placeholders})`;\n const rows = this.db.prepare(sql).all(...ids) as any[];\n return rows.map((row) => ({ ...row, frontmatter: JSON.parse(row.frontmatter) }));\n }\n\n /** Count notes by type using SQL aggregation (much faster than materializing all rows). */\n countByType(): Record<string, number> {\n const rows = this.db.prepare('SELECT type, COUNT(*) as count FROM notes GROUP BY type').all() as Array<{ type: string; count: number }>;\n const result: Record<string, number> = {};\n for (const row of rows) result[row.type] = row.count;\n return result;\n }\n\n /** Count spores grouped by observation_type. */\n sporeCountsByObservationType(): Record<string, number> {\n const rows = this.db.prepare(\n \"SELECT json_extract(frontmatter, '$.observation_type') as obs_type, COUNT(*) as count FROM notes WHERE type = 'spore' GROUP BY obs_type\"\n ).all() as Array<{ obs_type: string | null; count: number }>;\n const result: Record<string, number> = {};\n for (const row of rows) result[row.obs_type ?? 'unknown'] = row.count;\n return result;\n }\n\n close(): void {\n this.db.close();\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
formatNoteForPrompt,
|
|
4
|
+
formatNotesForPrompt,
|
|
5
|
+
indexNote,
|
|
6
|
+
loadPrompt,
|
|
7
|
+
stripReasoningTokens
|
|
8
|
+
} from "./chunk-24DOZEUJ.js";
|
|
5
9
|
import {
|
|
6
10
|
generateEmbedding
|
|
7
11
|
} from "./chunk-RGVBGTD6.js";
|
|
8
|
-
import {
|
|
9
|
-
loadPrompt,
|
|
10
|
-
stripReasoningTokens
|
|
11
|
-
} from "./chunk-KYL67SKZ.js";
|
|
12
12
|
import {
|
|
13
13
|
external_exports,
|
|
14
14
|
require_dist
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-MQSYSQ6T.js";
|
|
16
16
|
import {
|
|
17
17
|
EMBEDDING_INPUT_LIMIT,
|
|
18
18
|
LLM_REASONING_MODE,
|
|
19
19
|
SUPERSESSION_CANDIDATE_LIMIT,
|
|
20
20
|
SUPERSESSION_MAX_TOKENS,
|
|
21
21
|
SUPERSESSION_VECTOR_FETCH_LIMIT
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-6BSDCZ5Q.js";
|
|
23
23
|
import {
|
|
24
24
|
__toESM
|
|
25
25
|
} from "./chunk-PZUWP5VK.js";
|
|
@@ -103,10 +103,8 @@ async function checkSupersession(newSporeId, deps) {
|
|
|
103
103
|
return [];
|
|
104
104
|
}
|
|
105
105
|
const template = loadPrompt("supersession");
|
|
106
|
-
const newSporeText =
|
|
107
|
-
|
|
108
|
-
const candidatesText = filtered.map((c) => `[${c.id}] ${c.title}
|
|
109
|
-
${c.content}`).join("\n\n");
|
|
106
|
+
const newSporeText = formatNoteForPrompt(newSpore);
|
|
107
|
+
const candidatesText = formatNotesForPrompt(filtered);
|
|
110
108
|
const prompt = template.replace("{{new_spore}}", newSporeText).replace("{{candidates}}", candidatesText);
|
|
111
109
|
let responseText;
|
|
112
110
|
try {
|
|
@@ -156,4 +154,4 @@ export {
|
|
|
156
154
|
supersedeSpore,
|
|
157
155
|
checkSupersession
|
|
158
156
|
};
|
|
159
|
-
//# sourceMappingURL=chunk-
|
|
157
|
+
//# sourceMappingURL=chunk-UVGAVYWZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vault/curation.ts"],"sourcesContent":["/**\n * Vault curation — supersession detection pipeline.\n *\n * Given a newly written spore ID, finds older spores of the same observation_type\n * that have been rendered outdated, and marks them as superseded.\n */\n\nimport { z } from 'zod';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport YAML from 'yaml';\nimport type { MycoIndex, IndexedNote } from '../index/sqlite.js';\nimport type { VectorIndex } from '../index/vectors.js';\nimport type { LlmProvider, EmbeddingProvider } from '../intelligence/llm.js';\nimport { generateEmbedding } from '../intelligence/embeddings.js';\nimport { stripReasoningTokens } from '../intelligence/response.js';\nimport { indexNote } from '../index/rebuild.js';\nimport { loadPrompt, formatNoteForPrompt, formatNotesForPrompt } from '../prompts/index.js';\nimport {\n SUPERSESSION_CANDIDATE_LIMIT,\n SUPERSESSION_VECTOR_FETCH_LIMIT,\n SUPERSESSION_MAX_TOKENS,\n EMBEDDING_INPUT_LIMIT,\n LLM_REASONING_MODE,\n} from '../constants.js';\n\ntype LogLevel = 'debug' | 'info' | 'warn';\ntype LogFn = (level: LogLevel, message: string, data?: Record<string, unknown>) => void;\n\n/** Zod schema for validating LLM supersession responses. */\nexport const supersededIdsSchema = z.array(z.string());\n\n/** Marker string used to detect whether a supersession notice has already been appended. */\nexport const SUPERSESSION_NOTICE_MARKER = 'Superseded by::';\n\n/** Returns true if a spore should be considered active (including legacy spores without status). */\nexport function isActiveSpore(frontmatter: Record<string, unknown>): boolean {\n const status = frontmatter.status as string | undefined;\n return !status || status === 'active';\n}\n\n/**\n * Mark a single spore as superseded: update frontmatter + append notice in a\n * single read-modify-write, then re-index and remove from vector index.\n * Returns true if the write succeeded, false if the file was not found.\n */\nexport function supersedeSpore(\n targetId: string,\n newSporeId: string,\n targetPath: string,\n deps: { index: MycoIndex; vectorIndex: VectorIndex | null; vaultDir: string },\n): boolean {\n const fullPath = path.join(deps.vaultDir, targetPath);\n let fileContent: string;\n try {\n fileContent = fs.readFileSync(fullPath, 'utf-8');\n } catch {\n return false;\n }\n\n // Parse and update frontmatter\n const fmMatch = fileContent.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!fmMatch) return false;\n\n const parsed = YAML.parse(fmMatch[1]) as Record<string, unknown>;\n parsed.status = 'superseded';\n parsed.superseded_by = newSporeId;\n\n const fmYaml = YAML.stringify(parsed, { defaultStringType: 'QUOTE_DOUBLE', defaultKeyType: 'PLAIN' }).trim();\n let body = fileContent.slice(fmMatch[0].length);\n\n // Append supersession notice (idempotent: skip if already present)\n if (!body.includes(SUPERSESSION_NOTICE_MARKER)) {\n const notice = `\\n\\n> [!warning] Superseded\\n> This observation has been superseded.\\n\\n${SUPERSESSION_NOTICE_MARKER} [[${newSporeId}]]`;\n body = body.trimEnd() + notice + '\\n';\n }\n\n // Atomic write (temp file + rename)\n const tmp = `${fullPath}.tmp`;\n fs.writeFileSync(tmp, `---\\n${fmYaml}\\n---${body}`, 'utf-8');\n fs.renameSync(tmp, fullPath);\n\n // Re-index the updated file and remove stale vector\n indexNote(deps.index, deps.vaultDir, targetPath);\n deps.vectorIndex?.delete(targetId);\n\n return true;\n}\n\n/**\n * Check whether the newly written spore with `newSporeId` supersedes any\n * existing active spores of the same observation_type.\n *\n * Returns the list of spore IDs that were marked superseded.\n */\nexport async function checkSupersession(\n newSporeId: string,\n deps: {\n index: MycoIndex;\n vectorIndex: VectorIndex | null;\n embeddingProvider: EmbeddingProvider;\n llmProvider: LlmProvider | null;\n vaultDir: string;\n log?: LogFn;\n },\n): Promise<string[]> {\n const { index, vectorIndex, embeddingProvider, llmProvider, vaultDir, log } = deps;\n\n // Early-exit if no vector index or LLM available\n if (!vectorIndex || !llmProvider) {\n log?.('debug', 'checkSupersession: skipped — vectorIndex or llmProvider unavailable', { newSporeId });\n return [];\n }\n\n // Look up the new spore to get its content and observation_type\n const newSporeResults = index.queryByIds([newSporeId]);\n if (newSporeResults.length === 0) {\n log?.('warn', 'checkSupersession: new spore not found in index', { newSporeId });\n return [];\n }\n const newSpore = newSporeResults[0];\n const observationType = newSpore.frontmatter['observation_type'] as string | undefined;\n\n // Embed the spore content for similarity search\n const embeddingText = newSpore.content.slice(0, EMBEDDING_INPUT_LIMIT);\n const embeddingResult = await generateEmbedding(embeddingProvider, embeddingText);\n\n // Fetch candidate spore IDs from vector index\n const vectorResults = vectorIndex.search(embeddingResult.embedding, {\n type: 'spore',\n limit: SUPERSESSION_VECTOR_FETCH_LIMIT,\n });\n\n if (vectorResults.length === 0) {\n log?.('debug', 'checkSupersession: no vector results', { newSporeId });\n return [];\n }\n\n const candidateIds = vectorResults.map((r) => r.id);\n\n // Look up candidate notes and post-filter:\n // - same observation_type as the new spore\n // - active status (including legacy spores without status field)\n // - not the new spore itself\n const candidateNotes = index.queryByIds(candidateIds);\n const filtered = candidateNotes\n .filter((note) => {\n if (note.id === newSporeId) return false;\n if (!isActiveSpore(note.frontmatter)) return false;\n if (observationType && note.frontmatter['observation_type'] !== observationType) return false;\n return true;\n })\n .slice(0, SUPERSESSION_CANDIDATE_LIMIT);\n\n if (filtered.length === 0) {\n log?.('debug', 'checkSupersession: no candidates after filtering', { newSporeId, observationType });\n return [];\n }\n\n // Build the supersession prompt\n const template = loadPrompt('supersession');\n const newSporeText = formatNoteForPrompt(newSpore);\n const candidatesText = formatNotesForPrompt(filtered);\n\n const prompt = template\n .replace('{{new_spore}}', newSporeText)\n .replace('{{candidates}}', candidatesText);\n\n // Ask the LLM which candidates are superseded\n let responseText: string;\n try {\n const response = await llmProvider.summarize(prompt, {\n maxTokens: SUPERSESSION_MAX_TOKENS,\n reasoning: LLM_REASONING_MODE,\n });\n responseText = stripReasoningTokens(response.text);\n } catch (err) {\n log?.('warn', 'checkSupersession: LLM call failed', { newSporeId, error: String(err) });\n return [];\n }\n\n // Parse the LLM response as a JSON array of IDs\n let rawIds: unknown;\n try {\n rawIds = JSON.parse(responseText);\n } catch {\n log?.('warn', 'checkSupersession: failed to parse LLM response', { newSporeId, responseText });\n return [];\n }\n\n const parsed = supersededIdsSchema.safeParse(rawIds);\n if (!parsed.success) {\n log?.('warn', 'checkSupersession: LLM response failed schema validation', { newSporeId });\n return [];\n }\n\n // Filter to IDs that actually exist in the candidate list and are still active\n const candidateMap = new Map<string, IndexedNote>(filtered.map((c) => [c.id, c]));\n const validIds = parsed.data.filter((id) => candidateMap.has(id));\n\n if (validIds.length === 0) {\n return [];\n }\n\n // Mark each validated candidate as superseded\n const supersededIds: string[] = [];\n\n for (const id of validIds) {\n const candidate = candidateMap.get(id)!;\n const wrote = supersedeSpore(id, newSporeId, candidate.path, { index, vectorIndex, vaultDir });\n\n if (!wrote) {\n log?.('warn', 'checkSupersession: file not found for candidate, skipping', { id, path: candidate.path });\n continue;\n }\n\n supersededIds.push(id);\n log?.('info', 'checkSupersession: marked superseded', { supersededId: id, newSporeId });\n }\n\n return supersededIds;\n}\n"],"mappings":"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","names":["YAML"]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
MycoConfigSchema,
|
|
7
7
|
require_dist
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MQSYSQ6T.js";
|
|
9
9
|
import {
|
|
10
10
|
__toESM
|
|
11
11
|
} from "./chunk-PZUWP5VK.js";
|
|
@@ -144,4 +144,4 @@ async function run(args, vaultDir) {
|
|
|
144
144
|
export {
|
|
145
145
|
run
|
|
146
146
|
};
|
|
147
|
-
//# sourceMappingURL=chunk-
|
|
147
|
+
//# sourceMappingURL=chunk-YTANWAGE.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
VectorIndex
|
|
4
|
+
} from "./chunk-4RMSHZE4.js";
|
|
5
|
+
import {
|
|
6
|
+
isProcessAlive
|
|
7
|
+
} from "./chunk-2YBUL3IL.js";
|
|
8
|
+
|
|
9
|
+
// src/services/stats.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
var VECTOR_FALLBACK_DIMENSION = 1024;
|
|
13
|
+
function gatherStats(vaultDir, index, vectorIndex) {
|
|
14
|
+
const typeCounts = index.countByType();
|
|
15
|
+
const spore_counts = index.sporeCountsByObservationType();
|
|
16
|
+
let vector_count = 0;
|
|
17
|
+
if (vectorIndex) {
|
|
18
|
+
vector_count = vectorIndex.count();
|
|
19
|
+
} else {
|
|
20
|
+
const vecDb = path.join(vaultDir, "vectors.db");
|
|
21
|
+
if (fs.existsSync(vecDb)) {
|
|
22
|
+
try {
|
|
23
|
+
const vec = new VectorIndex(vecDb, VECTOR_FALLBACK_DIMENSION);
|
|
24
|
+
vector_count = vec.count();
|
|
25
|
+
vec.close();
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
let daemon = null;
|
|
31
|
+
const daemonPath = path.join(vaultDir, "daemon.json");
|
|
32
|
+
if (fs.existsSync(daemonPath)) {
|
|
33
|
+
try {
|
|
34
|
+
const info = JSON.parse(fs.readFileSync(daemonPath, "utf-8"));
|
|
35
|
+
daemon = {
|
|
36
|
+
pid: info.pid,
|
|
37
|
+
port: info.port,
|
|
38
|
+
started: info.started,
|
|
39
|
+
active_sessions: info.sessions || [],
|
|
40
|
+
alive: isProcessAlive(info.pid)
|
|
41
|
+
};
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
vault: {
|
|
47
|
+
path: vaultDir,
|
|
48
|
+
name: path.basename(vaultDir),
|
|
49
|
+
spore_counts,
|
|
50
|
+
session_count: typeCounts["session"] ?? 0,
|
|
51
|
+
plan_count: typeCounts["plan"] ?? 0
|
|
52
|
+
},
|
|
53
|
+
index: {
|
|
54
|
+
fts_entries: Object.values(typeCounts).reduce((sum, n) => sum + n, 0),
|
|
55
|
+
vector_count
|
|
56
|
+
},
|
|
57
|
+
daemon
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
gatherStats
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=chunk-ZMYNRTTD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/stats.ts"],"sourcesContent":["import { MycoIndex } from '../index/sqlite.js';\nimport { VectorIndex } from '../index/vectors.js';\nimport { isProcessAlive } from '../cli/shared.js';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/** Fallback dimension for opening VectorIndex when the actual dimension is unknown. */\nconst VECTOR_FALLBACK_DIMENSION = 1024;\n\nexport interface VaultStats {\n vault: {\n path: string;\n name: string;\n spore_counts: Record<string, number>;\n session_count: number;\n plan_count: number;\n };\n index: {\n fts_entries: number;\n vector_count: number;\n };\n daemon: {\n pid: number;\n port: number;\n started: string;\n active_sessions: string[];\n alive: boolean;\n } | null;\n}\n\nexport function gatherStats(vaultDir: string, index: MycoIndex, vectorIndex?: VectorIndex): VaultStats {\n const typeCounts = index.countByType();\n const spore_counts = index.sporeCountsByObservationType();\n\n let vector_count = 0;\n if (vectorIndex) {\n vector_count = vectorIndex.count();\n } else {\n const vecDb = path.join(vaultDir, 'vectors.db');\n if (fs.existsSync(vecDb)) {\n try {\n const vec = new VectorIndex(vecDb, VECTOR_FALLBACK_DIMENSION);\n vector_count = vec.count();\n vec.close();\n } catch { /* ignore */ }\n }\n }\n\n let daemon: VaultStats['daemon'] = null;\n const daemonPath = path.join(vaultDir, 'daemon.json');\n if (fs.existsSync(daemonPath)) {\n try {\n const info = JSON.parse(fs.readFileSync(daemonPath, 'utf-8'));\n daemon = {\n pid: info.pid,\n port: info.port,\n started: info.started,\n active_sessions: info.sessions || [],\n alive: isProcessAlive(info.pid),\n };\n } catch { /* ignore */ }\n }\n\n return {\n vault: {\n path: vaultDir,\n name: path.basename(vaultDir),\n spore_counts,\n session_count: typeCounts['session'] ?? 0,\n plan_count: typeCounts['plan'] ?? 0,\n },\n index: {\n fts_entries: Object.values(typeCounts).reduce((sum, n) => sum + n, 0),\n vector_count,\n },\n daemon,\n };\n}\n"],"mappings":";;;;;;;;;AAGA,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,IAAM,4BAA4B;AAuB3B,SAAS,YAAY,UAAkB,OAAkB,aAAuC;AACrG,QAAM,aAAa,MAAM,YAAY;AACrC,QAAM,eAAe,MAAM,6BAA6B;AAExD,MAAI,eAAe;AACnB,MAAI,aAAa;AACf,mBAAe,YAAY,MAAM;AAAA,EACnC,OAAO;AACL,UAAM,QAAQ,KAAK,KAAK,UAAU,YAAY;AAC9C,QAAI,GAAG,WAAW,KAAK,GAAG;AACxB,UAAI;AACF,cAAM,MAAM,IAAI,YAAY,OAAO,yBAAyB;AAC5D,uBAAe,IAAI,MAAM;AACzB,YAAI,MAAM;AAAA,MACZ,QAAQ;AAAA,MAAe;AAAA,IACzB;AAAA,EACF;AAEA,MAAI,SAA+B;AACnC,QAAM,aAAa,KAAK,KAAK,UAAU,aAAa;AACpD,MAAI,GAAG,WAAW,UAAU,GAAG;AAC7B,QAAI;AACF,YAAM,OAAO,KAAK,MAAM,GAAG,aAAa,YAAY,OAAO,CAAC;AAC5D,eAAS;AAAA,QACP,KAAK,KAAK;AAAA,QACV,MAAM,KAAK;AAAA,QACX,SAAS,KAAK;AAAA,QACd,iBAAiB,KAAK,YAAY,CAAC;AAAA,QACnC,OAAO,eAAe,KAAK,GAAG;AAAA,MAChC;AAAA,IACF,QAAQ;AAAA,IAAe;AAAA,EACzB;AAEA,SAAO;AAAA,IACL,OAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,KAAK,SAAS,QAAQ;AAAA,MAC5B;AAAA,MACA,eAAe,WAAW,SAAS,KAAK;AAAA,MACxC,YAAY,WAAW,MAAM,KAAK;AAAA,IACpC;AAAA,IACA,OAAO;AAAA,MACL,aAAa,OAAO,OAAO,UAAU,EAAE,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC;AAAA,MACpE;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
loadEnv
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2YBUL3IL.js";
|
|
6
6
|
import "./chunk-SAKJMNSR.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-QGJ2ZIUZ.js";
|
|
8
8
|
import {
|
|
9
9
|
resolveVaultDir
|
|
10
10
|
} from "./chunk-N33KUCFP.js";
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-5QWZT4AB.js";
|
|
12
|
+
import "./chunk-6BSDCZ5Q.js";
|
|
13
13
|
import "./chunk-PZUWP5VK.js";
|
|
14
14
|
|
|
15
15
|
// src/cli.ts
|
|
@@ -43,10 +43,10 @@ async function main() {
|
|
|
43
43
|
process.stdout.write(USAGE);
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
if (cmd === "init") return (await import("./init-
|
|
47
|
-
if (cmd === "detect-providers") return (await import("./detect-providers-
|
|
46
|
+
if (cmd === "init") return (await import("./init-TFLSATB3.js")).run(args);
|
|
47
|
+
if (cmd === "detect-providers") return (await import("./detect-providers-S3M5TAMW.js")).run(args);
|
|
48
48
|
if (cmd === "version" || cmd === "--version" || cmd === "-v") {
|
|
49
|
-
const { getPluginVersion } = await import("./version-
|
|
49
|
+
const { getPluginVersion } = await import("./version-RQLD7VBP.js");
|
|
50
50
|
console.log(getPluginVersion());
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
@@ -57,33 +57,33 @@ async function main() {
|
|
|
57
57
|
}
|
|
58
58
|
switch (cmd) {
|
|
59
59
|
case "config":
|
|
60
|
-
return (await import("./config-
|
|
60
|
+
return (await import("./config-G5GGT5A6.js")).run(args, vaultDir);
|
|
61
61
|
case "curate":
|
|
62
|
-
return (await import("./curate-
|
|
62
|
+
return (await import("./curate-6T5NKVXK.js")).run(args, vaultDir);
|
|
63
63
|
case "verify":
|
|
64
|
-
return (await import("./verify-
|
|
64
|
+
return (await import("./verify-7DW7LAND.js")).run(args, vaultDir);
|
|
65
65
|
case "stats":
|
|
66
|
-
return (await import("./stats-
|
|
66
|
+
return (await import("./stats-MKDIZFIQ.js")).run(args, vaultDir);
|
|
67
67
|
case "search":
|
|
68
|
-
return (await import("./search-
|
|
68
|
+
return (await import("./search-2BVRF54H.js")).run(args, vaultDir);
|
|
69
69
|
case "vectors":
|
|
70
|
-
return (await import("./search-
|
|
70
|
+
return (await import("./search-2BVRF54H.js")).runVectors(args, vaultDir);
|
|
71
71
|
case "session":
|
|
72
|
-
return (await import("./session-
|
|
72
|
+
return (await import("./session-F326AWCH.js")).run(args, vaultDir);
|
|
73
73
|
case "setup-llm":
|
|
74
|
-
return (await import("./setup-llm-
|
|
74
|
+
return (await import("./setup-llm-JOXBSLXC.js")).run(args, vaultDir);
|
|
75
75
|
case "setup-digest":
|
|
76
|
-
return (await import("./setup-digest-
|
|
76
|
+
return (await import("./setup-digest-YLZZGSSR.js")).run(args, vaultDir);
|
|
77
77
|
case "digest":
|
|
78
|
-
return (await import("./digest-
|
|
78
|
+
return (await import("./digest-O35VHYFP.js")).run(args, vaultDir);
|
|
79
79
|
case "restart":
|
|
80
|
-
return (await import("./restart-
|
|
80
|
+
return (await import("./restart-NLJLB52D.js")).run(args, vaultDir);
|
|
81
81
|
case "rebuild":
|
|
82
|
-
return (await import("./rebuild-
|
|
82
|
+
return (await import("./rebuild-7SH5GSNX.js")).run(args, vaultDir);
|
|
83
83
|
case "reprocess":
|
|
84
|
-
return (await import("./reprocess-
|
|
84
|
+
return (await import("./reprocess-Q4YH2ZBK.js")).run(args, vaultDir);
|
|
85
85
|
case "logs":
|
|
86
|
-
return (await import("./logs-
|
|
86
|
+
return (await import("./logs-IENORIYR.js")).run(args, vaultDir);
|
|
87
87
|
default:
|
|
88
88
|
console.error(`Unknown command: ${cmd}`);
|
|
89
89
|
process.stdout.write(USAGE);
|
|
@@ -94,4 +94,4 @@ main().catch((err) => {
|
|
|
94
94
|
console.error(`myco: ${err.message}`);
|
|
95
95
|
process.exit(1);
|
|
96
96
|
});
|
|
97
|
-
//# sourceMappingURL=cli-
|
|
97
|
+
//# sourceMappingURL=cli-K7SUTP7A.js.map
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
DaemonClient
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-2GSX3BK2.js";
|
|
5
|
+
import "./chunk-E7OBRBCQ.js";
|
|
6
|
+
import "./chunk-5QWZT4AB.js";
|
|
7
|
+
import "./chunk-6BSDCZ5Q.js";
|
|
8
8
|
import "./chunk-PZUWP5VK.js";
|
|
9
9
|
export {
|
|
10
10
|
DaemonClient
|
|
11
11
|
};
|
|
12
|
-
//# sourceMappingURL=client-
|
|
12
|
+
//# sourceMappingURL=client-YJMNTITQ.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
loadConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-P3WO3N3I.js";
|
|
5
5
|
import {
|
|
6
6
|
MycoConfigSchema,
|
|
7
7
|
require_dist
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MQSYSQ6T.js";
|
|
9
9
|
import {
|
|
10
10
|
__toESM
|
|
11
11
|
} from "./chunk-PZUWP5VK.js";
|
|
@@ -98,4 +98,4 @@ function parseValue(raw) {
|
|
|
98
98
|
export {
|
|
99
99
|
run
|
|
100
100
|
};
|
|
101
|
-
//# sourceMappingURL=config-
|
|
101
|
+
//# sourceMappingURL=config-G5GGT5A6.js.map
|