@goondocks/myco 0.4.3 → 0.4.4

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.
Files changed (90) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/dist/chunk-2AMAOSRF.js +105 -0
  4. package/dist/chunk-2AMAOSRF.js.map +1 -0
  5. package/dist/chunk-3F63SFZZ.js +381 -0
  6. package/dist/chunk-3F63SFZZ.js.map +1 -0
  7. package/dist/{chunk-WBT5DWGC.js → chunk-42R7KVAW.js} +2 -2
  8. package/dist/{chunk-GFBG73P4.js → chunk-5FIIK27E.js} +3 -3
  9. package/dist/{chunk-XCPQHC4X.js → chunk-6CAKKNGD.js} +2 -2
  10. package/dist/{chunk-I7PNZEBO.js → chunk-6LTNFMXO.js} +12 -1
  11. package/dist/{chunk-I7PNZEBO.js.map → chunk-6LTNFMXO.js.map} +1 -1
  12. package/dist/{chunk-V2OWD2VV.js → chunk-DKHYIA2V.js} +24 -146
  13. package/dist/chunk-DKHYIA2V.js.map +1 -0
  14. package/dist/{chunk-BNIYWCST.js → chunk-EQVQEFOA.js} +2 -2
  15. package/dist/{chunk-FPEDTLQ6.js → chunk-JJL6AMDA.js} +3 -101
  16. package/dist/chunk-JJL6AMDA.js.map +1 -0
  17. package/dist/{chunk-OUFSLZTX.js → chunk-KDWBZSOB.js} +21 -9
  18. package/dist/chunk-KDWBZSOB.js.map +1 -0
  19. package/dist/{chunk-67R6EMYD.js → chunk-OPO47BVS.js} +31 -52
  20. package/dist/chunk-OPO47BVS.js.map +1 -0
  21. package/dist/{chunk-IYFKPSRP.js → chunk-OSZRLHIJ.js} +3 -3
  22. package/dist/chunk-PD7LV22R.js +150 -0
  23. package/dist/chunk-PD7LV22R.js.map +1 -0
  24. package/dist/{chunk-JBD5KP5G.js → chunk-TDLQBGKA.js} +6 -2
  25. package/dist/chunk-TDLQBGKA.js.map +1 -0
  26. package/dist/{chunk-2GJFTIWX.js → chunk-TK2ZYIAL.js} +2 -2
  27. package/dist/{chunk-ZCBL5HER.js → chunk-XIIVIMFC.js} +2 -2
  28. package/dist/{cli-PMOFCZQL.js → cli-WOM4Z2Z4.js} +21 -18
  29. package/dist/cli-WOM4Z2Z4.js.map +1 -0
  30. package/dist/{client-5SUO2UYH.js → client-XCNF6NFT.js} +5 -5
  31. package/dist/{detect-providers-IRL2TTLK.js → detect-providers-CQSPTW2B.js} +3 -3
  32. package/dist/digest-WTS6S4XP.js +96 -0
  33. package/dist/digest-WTS6S4XP.js.map +1 -0
  34. package/dist/{init-NUF5UBUJ.js → init-VPLUEULI.js} +5 -5
  35. package/dist/{main-2XEBVUR6.js → main-OGXH6XWO.js} +230 -575
  36. package/dist/main-OGXH6XWO.js.map +1 -0
  37. package/dist/{rebuild-E6YFIRYZ.js → rebuild-Z4YUY6HT.js} +8 -7
  38. package/dist/{rebuild-E6YFIRYZ.js.map → rebuild-Z4YUY6HT.js.map} +1 -1
  39. package/dist/{reprocess-7G7KQWCN.js → reprocess-DMGPZTLC.js} +91 -20
  40. package/dist/reprocess-DMGPZTLC.js.map +1 -0
  41. package/dist/{restart-ABW4ZK3P.js → restart-QCQQ55KX.js} +6 -6
  42. package/dist/{search-MPD7SFK6.js → search-ACEFQOUW.js} +6 -6
  43. package/dist/{server-NZLZRITH.js → server-BQ3DWKZ6.js} +16 -14
  44. package/dist/{server-NZLZRITH.js.map → server-BQ3DWKZ6.js.map} +1 -1
  45. package/dist/{session-start-YB4A4PZB.js → session-start-BXRTKS4X.js} +6 -6
  46. package/dist/{setup-digest-K732MGOJ.js → setup-digest-EJXSQGZ5.js} +5 -5
  47. package/dist/{setup-llm-XCCH5LYD.js → setup-llm-P3MLWUDR.js} +5 -5
  48. package/dist/src/cli.js +4 -4
  49. package/dist/src/daemon/main.js +4 -4
  50. package/dist/src/hooks/post-tool-use.js +5 -5
  51. package/dist/src/hooks/session-end.js +5 -5
  52. package/dist/src/hooks/session-start.js +4 -4
  53. package/dist/src/hooks/stop.js +6 -6
  54. package/dist/src/hooks/stop.js.map +1 -1
  55. package/dist/src/hooks/user-prompt-submit.js +5 -5
  56. package/dist/src/mcp/server.js +4 -4
  57. package/dist/src/prompts/extraction.md +1 -1
  58. package/dist/src/prompts/summary.md +1 -11
  59. package/dist/{stats-6G7SN5YZ.js → stats-3FAP5FKV.js} +5 -5
  60. package/dist/{verify-JFHQH55Z.js → verify-3FTCOULE.js} +4 -4
  61. package/dist/{version-5B2TWXQJ.js → version-AL67JH7X.js} +4 -4
  62. package/package.json +1 -1
  63. package/skills/setup/SKILL.md +56 -28
  64. package/skills/setup/references/model-recommendations.md +49 -43
  65. package/dist/chunk-67R6EMYD.js.map +0 -1
  66. package/dist/chunk-FPEDTLQ6.js.map +0 -1
  67. package/dist/chunk-JBD5KP5G.js.map +0 -1
  68. package/dist/chunk-OUFSLZTX.js.map +0 -1
  69. package/dist/chunk-V2OWD2VV.js.map +0 -1
  70. package/dist/cli-PMOFCZQL.js.map +0 -1
  71. package/dist/main-2XEBVUR6.js.map +0 -1
  72. package/dist/reprocess-7G7KQWCN.js.map +0 -1
  73. /package/dist/{chunk-WBT5DWGC.js.map → chunk-42R7KVAW.js.map} +0 -0
  74. /package/dist/{chunk-GFBG73P4.js.map → chunk-5FIIK27E.js.map} +0 -0
  75. /package/dist/{chunk-XCPQHC4X.js.map → chunk-6CAKKNGD.js.map} +0 -0
  76. /package/dist/{chunk-BNIYWCST.js.map → chunk-EQVQEFOA.js.map} +0 -0
  77. /package/dist/{chunk-IYFKPSRP.js.map → chunk-OSZRLHIJ.js.map} +0 -0
  78. /package/dist/{chunk-2GJFTIWX.js.map → chunk-TK2ZYIAL.js.map} +0 -0
  79. /package/dist/{chunk-ZCBL5HER.js.map → chunk-XIIVIMFC.js.map} +0 -0
  80. /package/dist/{client-5SUO2UYH.js.map → client-XCNF6NFT.js.map} +0 -0
  81. /package/dist/{detect-providers-IRL2TTLK.js.map → detect-providers-CQSPTW2B.js.map} +0 -0
  82. /package/dist/{init-NUF5UBUJ.js.map → init-VPLUEULI.js.map} +0 -0
  83. /package/dist/{restart-ABW4ZK3P.js.map → restart-QCQQ55KX.js.map} +0 -0
  84. /package/dist/{search-MPD7SFK6.js.map → search-ACEFQOUW.js.map} +0 -0
  85. /package/dist/{session-start-YB4A4PZB.js.map → session-start-BXRTKS4X.js.map} +0 -0
  86. /package/dist/{setup-digest-K732MGOJ.js.map → setup-digest-EJXSQGZ5.js.map} +0 -0
  87. /package/dist/{setup-llm-XCCH5LYD.js.map → setup-llm-P3MLWUDR.js.map} +0 -0
  88. /package/dist/{stats-6G7SN5YZ.js.map → stats-3FAP5FKV.js.map} +0 -0
  89. /package/dist/{verify-JFHQH55Z.js.map → verify-3FTCOULE.js.map} +0 -0
  90. /package/dist/{version-5B2TWXQJ.js.map → version-AL67JH7X.js.map} +0 -0
@@ -1,15 +1,16 @@
1
1
  import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
2
2
  import {
3
3
  getPluginVersion
4
- } from "./chunk-2GJFTIWX.js";
4
+ } from "./chunk-TK2ZYIAL.js";
5
5
  import {
6
6
  AgentRegistry
7
- } from "./chunk-BNIYWCST.js";
7
+ } from "./chunk-EQVQEFOA.js";
8
8
  import {
9
9
  DAEMON_CLIENT_TIMEOUT_MS,
10
10
  DAEMON_HEALTH_CHECK_TIMEOUT_MS,
11
- DAEMON_HEALTH_RETRY_DELAYS
12
- } from "./chunk-JBD5KP5G.js";
11
+ DAEMON_HEALTH_RETRY_DELAYS,
12
+ DAEMON_STALE_GRACE_PERIOD_MS
13
+ } from "./chunk-TDLQBGKA.js";
13
14
 
14
15
  // src/hooks/client.ts
15
16
  import fs from "fs";
@@ -68,9 +69,16 @@ var DaemonClient = class {
68
69
  /**
69
70
  * Check if the daemon is running a stale version.
70
71
  * Returns true if the daemon's version doesn't match the current plugin version.
72
+ * Skips the check if daemon.json was written recently (grace period) to prevent
73
+ * rapid restart loops from concurrent hooks or session reloads.
71
74
  */
72
75
  async isStale() {
73
76
  try {
77
+ const jsonPath = path.join(this.vaultDir, "daemon.json");
78
+ const stat = fs.statSync(jsonPath);
79
+ if (Date.now() - stat.mtimeMs < DAEMON_STALE_GRACE_PERIOD_MS) {
80
+ return false;
81
+ }
74
82
  const info = this.readDaemonJson();
75
83
  if (!info) return false;
76
84
  const res = await fetch(`http://127.0.0.1:${info.port}/health`, {
@@ -101,11 +109,15 @@ var DaemonClient = class {
101
109
  }
102
110
  }
103
111
  /**
104
- * Ensure the daemon is running the current version. Spawns it if unhealthy
105
- * or restarts it if the version is stale. Returns true if healthy after this call.
112
+ * Ensure the daemon is running. Spawns it if unhealthy.
113
+ * When checkStale is true (default), also restarts a healthy daemon if its
114
+ * version doesn't match the current plugin version. Use checkStale: false
115
+ * for hooks that just need the daemon alive (e.g., stop) without triggering
116
+ * version-driven restarts.
106
117
  */
107
- async ensureRunning() {
108
- if (await this.isStale()) {
118
+ async ensureRunning(opts) {
119
+ const checkStale = opts?.checkStale ?? true;
120
+ if (checkStale && await this.isStale()) {
109
121
  this.killDaemon();
110
122
  await new Promise((r) => setTimeout(r, 200));
111
123
  } else if (await this.isHealthy()) {
@@ -166,4 +178,4 @@ var DaemonClient = class {
166
178
  export {
167
179
  DaemonClient
168
180
  };
169
- //# sourceMappingURL=chunk-OUFSLZTX.js.map
181
+ //# sourceMappingURL=chunk-KDWBZSOB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/client.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport { DAEMON_CLIENT_TIMEOUT_MS, DAEMON_HEALTH_CHECK_TIMEOUT_MS, DAEMON_HEALTH_RETRY_DELAYS, DAEMON_STALE_GRACE_PERIOD_MS } from '../constants.js';\nimport { AgentRegistry } from '../agents/registry.js';\nimport { getPluginVersion } from '../version.js';\n\ninterface DaemonInfo {\n pid: number;\n port: number;\n}\n\ninterface HealthResponse {\n myco: boolean;\n version?: string;\n}\n\ninterface ClientResult {\n ok: boolean;\n data?: any;\n}\n\nexport class DaemonClient {\n private vaultDir: string;\n\n constructor(vaultDir: string) {\n this.vaultDir = vaultDir;\n }\n\n async post(endpoint: string, body: unknown): Promise<ClientResult> {\n try {\n const info = this.readDaemonJson();\n if (!info) return { ok: false };\n\n const res = await fetch(`http://127.0.0.1:${info.port}${endpoint}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS),\n });\n\n if (!res.ok) return { ok: false };\n const data = await res.json();\n return { ok: true, data };\n } catch {\n return { ok: false };\n }\n }\n\n async get(endpoint: string): Promise<ClientResult> {\n try {\n const info = this.readDaemonJson();\n if (!info) return { ok: false };\n\n const res = await fetch(`http://127.0.0.1:${info.port}${endpoint}`, {\n signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS),\n });\n\n if (!res.ok) return { ok: false };\n const data = await res.json();\n return { ok: true, data };\n } catch {\n return { ok: false };\n }\n }\n\n async isHealthy(): Promise<boolean> {\n try {\n const info = this.readDaemonJson();\n if (!info) return false;\n\n const res = await fetch(`http://127.0.0.1:${info.port}/health`, {\n signal: AbortSignal.timeout(DAEMON_HEALTH_CHECK_TIMEOUT_MS),\n });\n if (!res.ok) return false;\n const data = await res.json() as HealthResponse;\n return data.myco === true;\n } catch {\n return false;\n }\n }\n\n /**\n * Check if the daemon is running a stale version.\n * Returns true if the daemon's version doesn't match the current plugin version.\n * Skips the check if daemon.json was written recently (grace period) to prevent\n * rapid restart loops from concurrent hooks or session reloads.\n */\n private async isStale(): Promise<boolean> {\n try {\n const jsonPath = path.join(this.vaultDir, 'daemon.json');\n const stat = fs.statSync(jsonPath);\n if (Date.now() - stat.mtimeMs < DAEMON_STALE_GRACE_PERIOD_MS) {\n return false;\n }\n\n const info = this.readDaemonJson();\n if (!info) return false;\n\n const res = await fetch(`http://127.0.0.1:${info.port}/health`, {\n signal: AbortSignal.timeout(DAEMON_HEALTH_CHECK_TIMEOUT_MS),\n });\n if (!res.ok) return false;\n const data = await res.json() as HealthResponse;\n if (!data.myco) return false;\n\n // No version in response = old daemon that predates this check\n if (!data.version) return true;\n\n return data.version !== getPluginVersion();\n } catch {\n return false;\n }\n }\n\n /**\n * Kill the running daemon process.\n */\n private killDaemon(): void {\n try {\n const info = this.readDaemonJson();\n if (!info) return;\n process.kill(info.pid, 'SIGTERM');\n } catch { /* already dead */ }\n try {\n fs.unlinkSync(path.join(this.vaultDir, 'daemon.json'));\n } catch { /* already gone */ }\n }\n\n /**\n * Ensure the daemon is running. Spawns it if unhealthy.\n * When checkStale is true (default), also restarts a healthy daemon if its\n * version doesn't match the current plugin version. Use checkStale: false\n * for hooks that just need the daemon alive (e.g., stop) without triggering\n * version-driven restarts.\n */\n async ensureRunning(opts?: { checkStale?: boolean }): Promise<boolean> {\n const checkStale = opts?.checkStale ?? true;\n\n if (checkStale && await this.isStale()) {\n this.killDaemon();\n // Brief pause for port release\n await new Promise((r) => setTimeout(r, 200));\n } else if (await this.isHealthy()) {\n return true;\n }\n\n this.spawnDaemon();\n\n for (const delay of DAEMON_HEALTH_RETRY_DELAYS) {\n await new Promise((r) => setTimeout(r, delay));\n if (await this.isHealthy()) return true;\n }\n return false;\n }\n\n spawnDaemon(): void {\n const daemonScript = this.resolveDaemonScript();\n if (!daemonScript || !fs.existsSync(daemonScript)) return;\n\n const child = spawn('node', [daemonScript, '--vault', this.vaultDir], {\n detached: true,\n stdio: 'ignore',\n });\n child.unref();\n }\n\n /**\n * Resolve the daemon entry script path.\n * Priority:\n * 1. Plugin root env var (set by the agent host) → dist/src/daemon/main.js\n * 2. Walk up from the current file to find the dist/ directory containing\n * the daemon entry. This handles both chunk files (dist/chunk-*.js) and\n * thin entry points (dist/src/hooks/*.js) after bundling.\n */\n private resolveDaemonScript(): string | undefined {\n const pluginRoot = new AgentRegistry().resolvePluginRoot();\n if (pluginRoot) {\n return path.join(pluginRoot, 'dist', 'src', 'daemon', 'main.js');\n }\n\n // Walk up from import.meta.dirname looking for the daemon entry\n let dir = import.meta.dirname;\n for (let i = 0; i < 5; i++) {\n const candidate = path.join(dir, 'dist', 'src', 'daemon', 'main.js');\n if (fs.existsSync(candidate)) return candidate;\n // Also check if we're already inside dist/\n const inDist = path.join(dir, 'src', 'daemon', 'main.js');\n if (fs.existsSync(inDist)) return inDist;\n dir = path.dirname(dir);\n }\n return undefined;\n }\n\n private readDaemonJson(): DaemonInfo | null {\n try {\n const jsonPath = path.join(this.vaultDir, 'daemon.json');\n const content = fs.readFileSync(jsonPath, 'utf-8');\n const info = JSON.parse(content);\n if (typeof info.port !== 'number') return null;\n return info as DaemonInfo;\n } catch {\n return null;\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -2,9 +2,8 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
2
2
  import {
3
3
  DAEMON_CLIENT_TIMEOUT_MS,
4
4
  EMBEDDING_REQUEST_TIMEOUT_MS,
5
- LLM_REQUEST_TIMEOUT_MS,
6
- estimateTokens
7
- } from "./chunk-JBD5KP5G.js";
5
+ LLM_REQUEST_TIMEOUT_MS
6
+ } from "./chunk-TDLQBGKA.js";
8
7
 
9
8
  // src/intelligence/ollama.ts
10
9
  var ENDPOINT_GENERATE = "/api/generate";
@@ -15,27 +14,23 @@ var OllamaBackend = class _OllamaBackend {
15
14
  name = "ollama";
16
15
  baseUrl;
17
16
  model;
18
- contextWindow;
19
17
  defaultMaxTokens;
20
18
  constructor(config) {
21
19
  this.baseUrl = config?.base_url ?? _OllamaBackend.DEFAULT_BASE_URL;
22
20
  this.model = config?.model ?? config?.summary_model ?? "llama3.2";
23
- this.contextWindow = config?.context_window ?? 8192;
24
21
  this.defaultMaxTokens = config?.max_tokens ?? 1024;
25
22
  }
26
23
  async summarize(prompt, opts) {
27
24
  const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;
28
- const contextLength = opts?.contextLength ?? this.contextWindow;
29
- const promptTokens = estimateTokens(prompt);
30
- const numCtx = Math.max(promptTokens + maxTokens, contextLength);
25
+ const options = { num_predict: maxTokens };
26
+ if (opts?.contextLength) {
27
+ options.num_ctx = opts.contextLength;
28
+ }
31
29
  const body = {
32
30
  model: this.model,
33
31
  prompt,
34
32
  stream: false,
35
- options: {
36
- num_ctx: numCtx,
37
- num_predict: maxTokens
38
- }
33
+ options
39
34
  };
40
35
  if (opts?.systemPrompt) {
41
36
  body.system = opts.systemPrompt;
@@ -103,7 +98,6 @@ var OllamaBackend = class _OllamaBackend {
103
98
  // src/intelligence/lm-studio.ts
104
99
  var ENDPOINT_CHAT = "/api/v1/chat";
105
100
  var ENDPOINT_MODELS_LOAD = "/api/v1/models/load";
106
- var ENDPOINT_MODELS_UNLOAD = "/api/v1/models/unload";
107
101
  var ENDPOINT_MODELS_LIST = "/v1/models";
108
102
  var ENDPOINT_MODELS_NATIVE = "/api/v1/models";
109
103
  var ENDPOINT_EMBEDDINGS = "/v1/embeddings";
@@ -112,7 +106,7 @@ var LmStudioBackend = class _LmStudioBackend {
112
106
  name = "lm-studio";
113
107
  baseUrl;
114
108
  model;
115
- loadedInstanceId = null;
109
+ instanceId = null;
116
110
  contextWindow;
117
111
  defaultMaxTokens;
118
112
  constructor(config) {
@@ -123,21 +117,22 @@ var LmStudioBackend = class _LmStudioBackend {
123
117
  }
124
118
  /**
125
119
  * Generate text using LM Studio's native REST API (/api/v1/chat).
126
- * Supports per-request context_length, reasoning control, and system_prompt.
120
+ * Routes to our specific instance by ID when available, with model name +
121
+ * context_length as fallback. This ensures correct routing when multiple
122
+ * daemons share the same LM Studio, and graceful degradation when our
123
+ * instance is evicted by idle TTL.
127
124
  */
128
125
  async summarize(prompt, opts) {
129
126
  const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;
127
+ const contextLength = opts?.contextLength ?? this.contextWindow;
130
128
  const body = {
131
- model: this.loadedInstanceId ?? this.model,
129
+ model: this.instanceId ?? this.model,
132
130
  input: prompt,
133
131
  max_output_tokens: maxTokens,
134
132
  store: false
135
133
  };
136
- if (!this.loadedInstanceId) {
137
- const contextLength = opts?.contextLength ?? this.contextWindow;
138
- if (contextLength) {
139
- body.context_length = contextLength;
140
- }
134
+ if (contextLength) {
135
+ body.context_length = contextLength;
141
136
  }
142
137
  if (opts?.systemPrompt) {
143
138
  body.system_prompt = opts.systemPrompt;
@@ -153,6 +148,9 @@ var LmStudioBackend = class _LmStudioBackend {
153
148
  });
154
149
  if (!response.ok) {
155
150
  const errorBody = await response.text().catch(() => "");
151
+ if (response.status === 404 && this.instanceId) {
152
+ this.instanceId = null;
153
+ }
156
154
  throw new Error(`LM Studio summarize failed: ${response.status} ${errorBody.slice(0, 500)}`);
157
155
  }
158
156
  const data = await response.json();
@@ -183,9 +181,13 @@ var LmStudioBackend = class _LmStudioBackend {
183
181
  }
184
182
  /**
185
183
  * Ensure a model instance is loaded with the desired settings.
186
- * First checks for an existing compatible instance to reuse (prevents
187
- * accumulation across daemon restarts), then loads a new one only if needed.
188
- * Unloads incompatible instances of the same model to prevent resource exhaustion.
184
+ * Called every digest cycle (not cached) so it recovers from idle TTL eviction.
185
+ *
186
+ * The load API is necessary to control offload_kv_cache_to_gpu a load-time
187
+ * setting that cannot be set per-request via the chat API.
188
+ *
189
+ * Multi-daemon safe: finds or loads our own compatible instance without
190
+ * touching instances from other daemons/projects. Routes by instance ID.
189
191
  */
190
192
  async ensureLoaded(contextLength, gpuKvCache) {
191
193
  const ctx = contextLength ?? this.contextWindow;
@@ -195,12 +197,10 @@ var LmStudioBackend = class _LmStudioBackend {
195
197
  const matchesContext = !ctx || instance.config.context_length === ctx;
196
198
  const matchesKvCache = instance.config.offload_kv_cache_to_gpu === kvCache;
197
199
  if (matchesContext && matchesKvCache) {
198
- this.loadedInstanceId = instance.id;
199
- await this.unloadIncompatibleInstances(instances, ctx, kvCache);
200
+ this.instanceId = instance.id;
200
201
  return;
201
202
  }
202
203
  }
203
- await this.unloadIncompatibleInstances(instances, ctx, kvCache);
204
204
  const body = {
205
205
  model: this.model,
206
206
  flash_attention: true,
@@ -220,9 +220,9 @@ var LmStudioBackend = class _LmStudioBackend {
220
220
  throw new Error(`LM Studio model load failed: ${response.status} ${errorBody.slice(0, 200)}`);
221
221
  }
222
222
  const loadResult = await response.json();
223
- const instanceId = loadResult.id ?? loadResult.instance_id ?? loadResult.model_instance_id;
224
- if (instanceId) {
225
- this.loadedInstanceId = instanceId;
223
+ const id = loadResult.instance_id ?? loadResult.id ?? loadResult.model_instance_id;
224
+ if (id) {
225
+ this.instanceId = id;
226
226
  }
227
227
  }
228
228
  /**
@@ -242,27 +242,6 @@ var LmStudioBackend = class _LmStudioBackend {
242
242
  return [];
243
243
  }
244
244
  }
245
- /**
246
- * Unload instances of this model that don't match the desired settings.
247
- * Best-effort — failures are silently ignored to avoid blocking the load path.
248
- */
249
- async unloadIncompatibleInstances(instances, contextLength, gpuKvCache) {
250
- for (const instance of instances) {
251
- const matchesContext = !contextLength || instance.config.context_length === contextLength;
252
- const matchesKvCache = instance.config.offload_kv_cache_to_gpu === gpuKvCache;
253
- if (!matchesContext || !matchesKvCache) {
254
- try {
255
- await fetch(`${this.baseUrl}${ENDPOINT_MODELS_UNLOAD}`, {
256
- method: "POST",
257
- headers: { "Content-Type": "application/json" },
258
- body: JSON.stringify({ model: instance.id }),
259
- signal: AbortSignal.timeout(DAEMON_CLIENT_TIMEOUT_MS)
260
- });
261
- } catch {
262
- }
263
- }
264
- }
265
- }
266
245
  async isAvailable() {
267
246
  try {
268
247
  const response = await fetch(`${this.baseUrl}${ENDPOINT_MODELS_LIST}`, {
@@ -291,4 +270,4 @@ export {
291
270
  OllamaBackend,
292
271
  LmStudioBackend
293
272
  };
294
- //# sourceMappingURL=chunk-67R6EMYD.js.map
273
+ //# sourceMappingURL=chunk-OPO47BVS.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\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 }\n\n async summarize(prompt: string, opts?: LlmRequestOptions): Promise<LlmResponse> {\n const maxTokens = opts?.maxTokens ?? this.defaultMaxTokens;\n\n // Only send num_ctx when explicitly requested (e.g., digest needing 65K).\n // Ollama reloads the model on ANY num_ctx change, so omitting it lets\n // requests use whatever context is already loaded — no reload thrashing.\n const options: Record<string, unknown> = { num_predict: maxTokens };\n if (opts?.contextLength) {\n options.num_ctx = opts.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":[]}
@@ -2,10 +2,10 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
2
2
  import {
3
3
  LmStudioBackend,
4
4
  OllamaBackend
5
- } from "./chunk-67R6EMYD.js";
5
+ } from "./chunk-OPO47BVS.js";
6
6
  import {
7
7
  LLM_REQUEST_TIMEOUT_MS
8
- } from "./chunk-JBD5KP5G.js";
8
+ } from "./chunk-TDLQBGKA.js";
9
9
 
10
10
  // node_modules/@anthropic-ai/sdk/internal/tslib.mjs
11
11
  function __classPrivateFieldSet(receiver, state, value, kind, f) {
@@ -4911,4 +4911,4 @@ export {
4911
4911
  createLlmProvider,
4912
4912
  createEmbeddingProvider
4913
4913
  };
4914
- //# sourceMappingURL=chunk-IYFKPSRP.js.map
4914
+ //# sourceMappingURL=chunk-OSZRLHIJ.js.map
@@ -0,0 +1,150 @@
1
+ import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
2
+ import {
3
+ ARTIFACT_TYPES
4
+ } from "./chunk-2AMAOSRF.js";
5
+ import {
6
+ CANDIDATE_CONTENT_PREVIEW
7
+ } from "./chunk-TDLQBGKA.js";
8
+
9
+ // src/prompts/index.ts
10
+ import fs from "fs";
11
+ import path from "path";
12
+ import { fileURLToPath } from "url";
13
+ function resolvePromptsDir() {
14
+ let dir = path.dirname(fileURLToPath(import.meta.url));
15
+ for (let i = 0; i < 5; i++) {
16
+ if (fs.existsSync(path.join(dir, "package.json"))) {
17
+ return path.join(dir, "dist", "src", "prompts");
18
+ }
19
+ if (fs.existsSync(path.join(dir, "extraction.md"))) {
20
+ return dir;
21
+ }
22
+ dir = path.dirname(dir);
23
+ }
24
+ return path.dirname(fileURLToPath(import.meta.url));
25
+ }
26
+ var PROMPTS_DIR = resolvePromptsDir();
27
+ var promptCache = /* @__PURE__ */ new Map();
28
+ function loadPrompt(name) {
29
+ let cached = promptCache.get(name);
30
+ if (!cached) {
31
+ cached = fs.readFileSync(path.join(PROMPTS_DIR, `${name}.md`), "utf-8").trim();
32
+ promptCache.set(name, cached);
33
+ }
34
+ return cached;
35
+ }
36
+ function interpolate(template, vars) {
37
+ let result = template;
38
+ for (const [key, value] of Object.entries(vars)) {
39
+ result = result.replaceAll(`{{${key}}}`, value);
40
+ }
41
+ return result;
42
+ }
43
+ function buildExtractionPrompt(sessionId, eventCount, toolSummary, maxTokens) {
44
+ return interpolate(loadPrompt("extraction"), {
45
+ sessionId,
46
+ eventCount: String(eventCount),
47
+ toolSummary,
48
+ maxTokens: String(maxTokens ?? 2048)
49
+ });
50
+ }
51
+ function buildSummaryPrompt(sessionId, user, content, maxTokens) {
52
+ return interpolate(loadPrompt("summary"), {
53
+ sessionId,
54
+ user,
55
+ content,
56
+ maxTokens: String(maxTokens ?? 1024)
57
+ });
58
+ }
59
+ function buildTitlePrompt(summary, sessionId) {
60
+ return interpolate(loadPrompt("title"), {
61
+ summary,
62
+ sessionId
63
+ });
64
+ }
65
+ var ARTIFACT_TYPE_DESCRIPTIONS = [
66
+ '"spec" \u2014 Design specifications, architecture documents',
67
+ '"plan" \u2014 Implementation plans, roadmaps',
68
+ '"rfc" \u2014 Requests for comment, proposals',
69
+ '"doc" \u2014 Documentation, guides, READMEs',
70
+ '"other" \u2014 Other substantive documents'
71
+ ];
72
+ function buildSimilarityPrompt(currentSummary, candidateSummary) {
73
+ return interpolate(loadPrompt("session-similarity"), {
74
+ currentSummary,
75
+ candidateSummary
76
+ });
77
+ }
78
+ function buildClassificationPrompt(sessionId, candidates, maxTokens) {
79
+ const fileList = candidates.map((c) => {
80
+ const truncated = c.content.slice(0, CANDIDATE_CONTENT_PREVIEW);
81
+ return `### ${c.path}
82
+ \`\`\`
83
+ ${truncated}
84
+ \`\`\``;
85
+ }).join("\n\n");
86
+ return interpolate(loadPrompt("classification"), {
87
+ sessionId,
88
+ fileList,
89
+ artifactTypes: ARTIFACT_TYPE_DESCRIPTIONS.map((d) => `- ${d}`).join("\n"),
90
+ validTypes: ARTIFACT_TYPES.join("|"),
91
+ maxTokens: String(maxTokens ?? 1024)
92
+ });
93
+ }
94
+
95
+ // src/intelligence/response.ts
96
+ var REASONING_PATTERNS = [
97
+ // <think>...</think>answer (DeepSeek, Qwen, GLM, many others)
98
+ /<think>[\s\S]*?<\/think>\s*/gi,
99
+ // Implicit opening: reasoning...</think>answer (GLM-4.7 observed)
100
+ /^[\s\S]*?<\/think>\s*/i,
101
+ // <reasoning>...</reasoning>answer
102
+ /<reasoning>[\s\S]*?<\/reasoning>\s*/gi,
103
+ // <|thinking|>...<|/thinking|>answer
104
+ /<\|thinking\|>[\s\S]*?<\|\/thinking\|>\s*/gi,
105
+ // Plain-text "Thinking Process:" block followed by actual content
106
+ // (Qwen 3.5 via LM Studio without native thinking mode)
107
+ // Matches from "Thinking Process:" up to the last numbered step, then the synthesis follows
108
+ /^Thinking Process:[\s\S]*?(?=\n(?:## |# |\*\*[A-Z]))/i
109
+ ];
110
+ function stripReasoningTokens(text) {
111
+ if (!text) return text;
112
+ for (const pattern of REASONING_PATTERNS) {
113
+ const stripped = text.replace(pattern, "").trim();
114
+ if (stripped && stripped !== text.trim()) {
115
+ return stripped;
116
+ }
117
+ }
118
+ return text;
119
+ }
120
+ function extractJson(text) {
121
+ const cleaned = stripReasoningTokens(text);
122
+ const fenceMatch = cleaned.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
123
+ if (fenceMatch) {
124
+ return JSON.parse(fenceMatch[1].trim());
125
+ }
126
+ const objectMatch = cleaned.match(/\{[\s\S]*\}/);
127
+ if (objectMatch) {
128
+ return JSON.parse(objectMatch[0]);
129
+ }
130
+ return JSON.parse(cleaned);
131
+ }
132
+ function extractNumber(text) {
133
+ const cleaned = stripReasoningTokens(text).trim();
134
+ const match = cleaned.match(/(\d+\.?\d*)/);
135
+ if (match) return parseFloat(match[1]);
136
+ return parseFloat(cleaned);
137
+ }
138
+
139
+ export {
140
+ loadPrompt,
141
+ buildExtractionPrompt,
142
+ buildSummaryPrompt,
143
+ buildTitlePrompt,
144
+ buildSimilarityPrompt,
145
+ buildClassificationPrompt,
146
+ stripReasoningTokens,
147
+ extractJson,
148
+ extractNumber
149
+ };
150
+ //# sourceMappingURL=chunk-PD7LV22R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/prompts/index.ts","../src/intelligence/response.ts"],"sourcesContent":["/**\n * Prompt loader — reads .md templates from disk and interpolates variables.\n * Prompts are markdown files in this directory, not TypeScript strings.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { ARTIFACT_TYPES } from '../vault/types.js';\nimport { CANDIDATE_CONTENT_PREVIEW } from '../constants.js';\n\n/**\n * Resolve the prompts directory. With tsup code-splitting, import.meta.url\n * points to a chunk file (dist/chunk-XXXX.js), not dist/src/prompts/.\n * Walk up from the current file to find package.json, then use dist/src/prompts/.\n */\nfunction resolvePromptsDir(): string {\n let dir = path.dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 5; i++) {\n if (fs.existsSync(path.join(dir, 'package.json'))) {\n return path.join(dir, 'dist', 'src', 'prompts');\n }\n // Also check if we're already in the right place (tsc output or dev mode)\n if (fs.existsSync(path.join(dir, 'extraction.md'))) {\n return dir;\n }\n dir = path.dirname(dir);\n }\n // Final fallback: adjacent to current file (works with tsc)\n return path.dirname(fileURLToPath(import.meta.url));\n}\n\nconst PROMPTS_DIR = resolvePromptsDir();\n\nconst promptCache = new Map<string, string>();\n\nexport function loadPrompt(name: string): string {\n let cached = promptCache.get(name);\n if (!cached) {\n cached = fs.readFileSync(path.join(PROMPTS_DIR, `${name}.md`), 'utf-8').trim();\n promptCache.set(name, cached);\n }\n return cached;\n}\n\nfunction interpolate(template: string, vars: Record<string, string>): string {\n let result = template;\n for (const [key, value] of Object.entries(vars)) {\n result = result.replaceAll(`{{${key}}}`, value);\n }\n return result;\n}\n\n// --- Prompt builders ---\n\nexport function buildExtractionPrompt(\n sessionId: string,\n eventCount: number,\n toolSummary: string,\n maxTokens?: number,\n): string {\n return interpolate(loadPrompt('extraction'), {\n sessionId,\n eventCount: String(eventCount),\n toolSummary,\n maxTokens: String(maxTokens ?? 2048),\n });\n}\n\nexport function buildSummaryPrompt(\n sessionId: string,\n user: string,\n content: string,\n maxTokens?: number,\n): string {\n return interpolate(loadPrompt('summary'), {\n sessionId,\n user,\n content,\n maxTokens: String(maxTokens ?? 1024),\n });\n}\n\nexport function buildTitlePrompt(\n summary: string,\n sessionId: string,\n): string {\n return interpolate(loadPrompt('title'), {\n summary,\n sessionId,\n });\n}\n\nconst ARTIFACT_TYPE_DESCRIPTIONS = [\n '\"spec\" — Design specifications, architecture documents',\n '\"plan\" — Implementation plans, roadmaps',\n '\"rfc\" — Requests for comment, proposals',\n '\"doc\" — Documentation, guides, READMEs',\n '\"other\" — Other substantive documents',\n];\n\nexport function buildSimilarityPrompt(\n currentSummary: string,\n candidateSummary: string,\n): string {\n return interpolate(loadPrompt('session-similarity'), {\n currentSummary,\n candidateSummary,\n });\n}\n\nexport function buildClassificationPrompt(\n sessionId: string,\n candidates: Array<{ path: string; content: string }>,\n maxTokens?: number,\n): string {\n const fileList = candidates\n .map((c) => {\n const truncated = c.content.slice(0, CANDIDATE_CONTENT_PREVIEW);\n return `### ${c.path}\\n\\`\\`\\`\\n${truncated}\\n\\`\\`\\``;\n })\n .join('\\n\\n');\n\n return interpolate(loadPrompt('classification'), {\n sessionId,\n fileList,\n artifactTypes: ARTIFACT_TYPE_DESCRIPTIONS.map((d) => `- ${d}`).join('\\n'),\n validTypes: ARTIFACT_TYPES.join('|'),\n maxTokens: String(maxTokens ?? 1024),\n });\n}\n","/**\n * Clean LLM response text before parsing.\n *\n * Reasoning models (DeepSeek, Qwen, GLM, etc.) embed chain-of-thought\n * in the response using special tags. These must be stripped before\n * JSON parsing or value extraction.\n */\n\n// Patterns for reasoning model chain-of-thought tokens.\n// Order matters: most specific patterns first.\nconst REASONING_PATTERNS = [\n // <think>...</think>answer (DeepSeek, Qwen, GLM, many others)\n /<think>[\\s\\S]*?<\\/think>\\s*/gi,\n // Implicit opening: reasoning...</think>answer (GLM-4.7 observed)\n /^[\\s\\S]*?<\\/think>\\s*/i,\n // <reasoning>...</reasoning>answer\n /<reasoning>[\\s\\S]*?<\\/reasoning>\\s*/gi,\n // <|thinking|>...<|/thinking|>answer\n /<\\|thinking\\|>[\\s\\S]*?<\\|\\/thinking\\|>\\s*/gi,\n // Plain-text \"Thinking Process:\" block followed by actual content\n // (Qwen 3.5 via LM Studio without native thinking mode)\n // Matches from \"Thinking Process:\" up to the last numbered step, then the synthesis follows\n /^Thinking Process:[\\s\\S]*?(?=\\n(?:## |# |\\*\\*[A-Z]))/i,\n];\n\n/**\n * Strip reasoning/chain-of-thought tokens from LLM response text.\n * Returns the final answer without the thinking process.\n */\nexport function stripReasoningTokens(text: string): string {\n if (!text) return text;\n\n for (const pattern of REASONING_PATTERNS) {\n const stripped = text.replace(pattern, '').trim();\n if (stripped && stripped !== text.trim()) {\n return stripped;\n }\n }\n\n return text;\n}\n\n/**\n * Extract JSON from an LLM response that may contain markdown fences,\n * reasoning tokens, or other wrapper text.\n *\n * Tries in order:\n * 1. Strip reasoning tokens\n * 2. Extract from ```json ... ``` code fences\n * 3. Find bare {...} JSON object\n * 4. Parse the cleaned text directly\n */\nexport function extractJson(text: string): unknown {\n const cleaned = stripReasoningTokens(text);\n\n // Try code fence extraction\n const fenceMatch = cleaned.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?```/);\n if (fenceMatch) {\n return JSON.parse(fenceMatch[1].trim());\n }\n\n // Try bare JSON object\n const objectMatch = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (objectMatch) {\n return JSON.parse(objectMatch[0]);\n }\n\n // Try direct parse\n return JSON.parse(cleaned);\n}\n\n/**\n * Extract a numeric value from an LLM response that may contain\n * reasoning tokens or extra text around the number.\n */\nexport function extractNumber(text: string): number {\n const cleaned = stripReasoningTokens(text).trim();\n const match = cleaned.match(/(\\d+\\.?\\d*)/);\n if (match) return parseFloat(match[1]);\n return parseFloat(cleaned);\n}\n"],"mappings":";;;;;;;;;AAKA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAS9B,SAAS,oBAA4B;AACnC,MAAI,MAAM,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AACrD,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,GAAG,WAAW,KAAK,KAAK,KAAK,cAAc,CAAC,GAAG;AACjD,aAAO,KAAK,KAAK,KAAK,QAAQ,OAAO,SAAS;AAAA,IAChD;AAEA,QAAI,GAAG,WAAW,KAAK,KAAK,KAAK,eAAe,CAAC,GAAG;AAClD,aAAO;AAAA,IACT;AACA,UAAM,KAAK,QAAQ,GAAG;AAAA,EACxB;AAEA,SAAO,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AACpD;AAEA,IAAM,cAAc,kBAAkB;AAEtC,IAAM,cAAc,oBAAI,IAAoB;AAErC,SAAS,WAAW,MAAsB;AAC/C,MAAI,SAAS,YAAY,IAAI,IAAI;AACjC,MAAI,CAAC,QAAQ;AACX,aAAS,GAAG,aAAa,KAAK,KAAK,aAAa,GAAG,IAAI,KAAK,GAAG,OAAO,EAAE,KAAK;AAC7E,gBAAY,IAAI,MAAM,MAAM;AAAA,EAC9B;AACA,SAAO;AACT;AAEA,SAAS,YAAY,UAAkB,MAAsC;AAC3E,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,aAAS,OAAO,WAAW,KAAK,GAAG,MAAM,KAAK;AAAA,EAChD;AACA,SAAO;AACT;AAIO,SAAS,sBACd,WACA,YACA,aACA,WACQ;AACR,SAAO,YAAY,WAAW,YAAY,GAAG;AAAA,IAC3C;AAAA,IACA,YAAY,OAAO,UAAU;AAAA,IAC7B;AAAA,IACA,WAAW,OAAO,aAAa,IAAI;AAAA,EACrC,CAAC;AACH;AAEO,SAAS,mBACd,WACA,MACA,SACA,WACQ;AACR,SAAO,YAAY,WAAW,SAAS,GAAG;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,OAAO,aAAa,IAAI;AAAA,EACrC,CAAC;AACH;AAEO,SAAS,iBACd,SACA,WACQ;AACR,SAAO,YAAY,WAAW,OAAO,GAAG;AAAA,IACtC;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,IAAM,6BAA6B;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,sBACd,gBACA,kBACQ;AACR,SAAO,YAAY,WAAW,oBAAoB,GAAG;AAAA,IACnD;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEO,SAAS,0BACd,WACA,YACA,WACQ;AACR,QAAM,WAAW,WACd,IAAI,CAAC,MAAM;AACV,UAAM,YAAY,EAAE,QAAQ,MAAM,GAAG,yBAAyB;AAC9D,WAAO,OAAO,EAAE,IAAI;AAAA;AAAA,EAAa,SAAS;AAAA;AAAA,EAC5C,CAAC,EACA,KAAK,MAAM;AAEd,SAAO,YAAY,WAAW,gBAAgB,GAAG;AAAA,IAC/C;AAAA,IACA;AAAA,IACA,eAAe,2BAA2B,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAAA,IACxE,YAAY,eAAe,KAAK,GAAG;AAAA,IACnC,WAAW,OAAO,aAAa,IAAI;AAAA,EACrC,CAAC;AACH;;;ACxHA,IAAM,qBAAqB;AAAA;AAAA,EAEzB;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA,EAIA;AACF;AAMO,SAAS,qBAAqB,MAAsB;AACzD,MAAI,CAAC,KAAM,QAAO;AAElB,aAAW,WAAW,oBAAoB;AACxC,UAAM,WAAW,KAAK,QAAQ,SAAS,EAAE,EAAE,KAAK;AAChD,QAAI,YAAY,aAAa,KAAK,KAAK,GAAG;AACxC,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAYO,SAAS,YAAY,MAAuB;AACjD,QAAM,UAAU,qBAAqB,IAAI;AAGzC,QAAM,aAAa,QAAQ,MAAM,oCAAoC;AACrE,MAAI,YAAY;AACd,WAAO,KAAK,MAAM,WAAW,CAAC,EAAE,KAAK,CAAC;AAAA,EACxC;AAGA,QAAM,cAAc,QAAQ,MAAM,aAAa;AAC/C,MAAI,aAAa;AACf,WAAO,KAAK,MAAM,YAAY,CAAC,CAAC;AAAA,EAClC;AAGA,SAAO,KAAK,MAAM,OAAO;AAC3B;AAMO,SAAS,cAAc,MAAsB;AAClD,QAAM,UAAU,qBAAqB,IAAI,EAAE,KAAK;AAChD,QAAM,QAAQ,QAAQ,MAAM,aAAa;AACzC,MAAI,MAAO,QAAO,WAAW,MAAM,CAAC,CAAC;AACrC,SAAO,WAAW,OAAO;AAC3B;","names":[]}
@@ -26,6 +26,7 @@ var FILE_WATCH_STABILITY_MS = 1e3;
26
26
  var PROVIDER_DETECT_TIMEOUT_MS = 3e3;
27
27
  var STALE_BUFFER_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
28
28
  var DAEMON_HEALTH_RETRY_DELAYS = [100, 200, 400, 800, 1500];
29
+ var DAEMON_STALE_GRACE_PERIOD_MS = 6e4;
29
30
  var MAX_SLUG_LENGTH = 100;
30
31
  var CANDIDATE_CONTENT_PREVIEW = 2e3;
31
32
  var LINEAGE_RECENT_SESSIONS_LIMIT = 5;
@@ -51,6 +52,7 @@ var DIGEST_SUBSTRATE_TYPE_WEIGHTS = {
51
52
  artifact: 1,
52
53
  team: 1
53
54
  };
55
+ var LLM_REASONING_MODE = "off";
54
56
 
55
57
  export {
56
58
  CHARS_PER_TOKEN,
@@ -76,6 +78,7 @@ export {
76
78
  PROVIDER_DETECT_TIMEOUT_MS,
77
79
  STALE_BUFFER_MAX_AGE_MS,
78
80
  DAEMON_HEALTH_RETRY_DELAYS,
81
+ DAEMON_STALE_GRACE_PERIOD_MS,
79
82
  MAX_SLUG_LENGTH,
80
83
  CANDIDATE_CONTENT_PREVIEW,
81
84
  LINEAGE_RECENT_SESSIONS_LIMIT,
@@ -89,6 +92,7 @@ export {
89
92
  MCP_LOGS_DEFAULT_LIMIT,
90
93
  DIGEST_TIERS,
91
94
  DIGEST_TIER_MIN_CONTEXT,
92
- DIGEST_SUBSTRATE_TYPE_WEIGHTS
95
+ DIGEST_SUBSTRATE_TYPE_WEIGHTS,
96
+ LLM_REASONING_MODE
93
97
  };
94
- //# sourceMappingURL=chunk-JBD5KP5G.js.map
98
+ //# sourceMappingURL=chunk-TDLQBGKA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts"],"sourcesContent":["/**\n * Shared constants for the Myco codebase.\n * Per CLAUDE.md: \"No Magic Literals — Numeric and string constants\n * MUST NOT appear inline in logic.\"\n */\n\n// --- Token estimation ---\n/** Approximate characters per token for the chars/4 heuristic. */\nexport const CHARS_PER_TOKEN = 4;\n\n/** Estimate token count from character length using the CHARS_PER_TOKEN heuristic. */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / CHARS_PER_TOKEN);\n}\n\n// --- Embedding ---\n/** Max characters of text sent to the embedding model. */\nexport const EMBEDDING_INPUT_LIMIT = 8000;\n\n// --- Truncation limits (display/preview) ---\n/** Max chars for a user prompt preview in event summaries. */\nexport const PROMPT_PREVIEW_CHARS = 300;\n/** Max chars for an AI response preview in event summaries. */\nexport const AI_RESPONSE_PREVIEW_CHARS = 500;\n/** Max chars for a command string preview. */\nexport const COMMAND_PREVIEW_CHARS = 80;\n/** Max chars for a content snippet in search results. */\nexport const CONTENT_SNIPPET_CHARS = 120;\n/** Max chars for a tool output preview in hooks. */\nexport const TOOL_OUTPUT_PREVIEW_CHARS = 200;\n/** Max chars for a session summary preview in MCP tools. */\nexport const SESSION_SUMMARY_PREVIEW_CHARS = 300;\n/** Max chars for a recall summary preview. */\nexport const RECALL_SUMMARY_PREVIEW_CHARS = 200;\n\n// --- Context injection layer budgets (chars, not tokens — used with .slice()) ---\nexport const CONTEXT_PLAN_PREVIEW_CHARS = 100;\nexport const CONTEXT_SESSION_PREVIEW_CHARS = 80;\nexport const CONTEXT_SPORE_PREVIEW_CHARS = 80;\n\n// --- Processor maxTokens budgets ---\n/** Response token budget for observation extraction. */\nexport const EXTRACTION_MAX_TOKENS = 2048;\n/** Response token budget for session summary. */\nexport const SUMMARY_MAX_TOKENS = 512;\n/** Response token budget for session title generation. */\nexport const TITLE_MAX_TOKENS = 32;\n/** Response token budget for artifact classification. */\nexport const CLASSIFICATION_MAX_TOKENS = 1024;\n\n// --- Timeouts ---\n/** Daemon client HTTP request timeout (ms). */\nexport const DAEMON_CLIENT_TIMEOUT_MS = 2000;\n/** Health check timeout (ms) — fail fast if daemon isn't responding. */\nexport const DAEMON_HEALTH_CHECK_TIMEOUT_MS = 500;\n/** LLM request timeout (ms). All LLM calls are background daemon work — no need to be aggressive. */\nexport const LLM_REQUEST_TIMEOUT_MS = 180_000;\n/** Embedding request timeout (ms). Embeddings run in background batch processing — generous timeout. */\nexport const EMBEDDING_REQUEST_TIMEOUT_MS = 60_000;\n/** Digest LLM request timeout (ms). Digest cycles use large context windows and may need model loading time. */\nexport const DIGEST_LLM_REQUEST_TIMEOUT_MS = 600_000;\n/** Stdin read timeout for hooks (ms). */\nexport const STDIN_TIMEOUT_MS = 100;\n/** Chokidar write stability threshold (ms). */\nexport const FILE_WATCH_STABILITY_MS = 1000;\n/** Provider detection timeout for detect-providers CLI command (ms). */\nexport const PROVIDER_DETECT_TIMEOUT_MS = 3000;\n\n// --- Buffer cleanup ---\n/** Max age for stale buffer files before cleanup (ms). */\nexport const STALE_BUFFER_MAX_AGE_MS = 24 * 60 * 60 * 1000;\n\n// --- Retry backoff ---\n/** Retry delays for daemon health check (ms). */\nexport const DAEMON_HEALTH_RETRY_DELAYS = [100, 200, 400, 800, 1500];\n\n/** Grace period after daemon.json is written before stale checks can trigger a restart (ms).\n * Prevents rapid restart loops from concurrent hooks or session reloads. */\nexport const DAEMON_STALE_GRACE_PERIOD_MS = 60_000;\n\n// --- Slug limits ---\n/** Max length for slugified artifact IDs. */\nexport const MAX_SLUG_LENGTH = 100;\n\n// --- Content preview for classification prompt ---\n/** Max chars of file content per candidate in classification prompt. */\nexport const CANDIDATE_CONTENT_PREVIEW = 2000;\n\n// --- Transcript mining ---\n/** Minimum content length to consider a transcript entry meaningful. */\nexport const MIN_TRANSCRIPT_CONTENT_LENGTH = 10;\n\n// --- Query limits ---\n/** Max recent sessions to check for lineage heuristics. */\nexport const LINEAGE_RECENT_SESSIONS_LIMIT = 5;\n/** Max related spores to query for session notes. */\nexport const RELATED_SPORES_LIMIT = 50;\n\n// --- Context injection ---\n/** Max active plans to inject at session start. */\nexport const SESSION_CONTEXT_MAX_PLANS = 3;\n/** Max spores to inject per prompt. */\nexport const PROMPT_CONTEXT_MAX_SPORES = 3;\n/** Minimum similarity score for prompt context injection (0-1). */\nexport const PROMPT_CONTEXT_MIN_SIMILARITY = 0.3;\n/** Max token budget for session-start context injection. */\nexport const SESSION_CONTEXT_MAX_TOKENS = 500;\n/** Max token budget for per-prompt context injection. */\nexport const PROMPT_CONTEXT_MAX_TOKENS = 300;\n/** Minimum prompt length to trigger context search. */\nexport const PROMPT_CONTEXT_MIN_LENGTH = 10;\n\n// --- MCP tool defaults ---\n/** Default result limit for myco_search. */\nexport const MCP_SEARCH_DEFAULT_LIMIT = 10;\n/** Default result limit for myco_sessions. */\nexport const MCP_SESSIONS_DEFAULT_LIMIT = 20;\n/** Default result limit for myco_logs. */\nexport const MCP_LOGS_DEFAULT_LIMIT = 50;\n\n// --- Digest — Tiers ---\n/** Available token-budget tiers for digest synthesis. */\nexport const DIGEST_TIERS = [1500, 3000, 5000, 10000] as const;\nexport type DigestTier = (typeof DIGEST_TIERS)[number];\n\n// --- Digest — Context window minimums per tier ---\n/** Minimum context window (tokens) required to run a digest at a given tier. */\nexport const DIGEST_TIER_MIN_CONTEXT: Record<number, number> = {\n 1500: 6500,\n 3000: 11500,\n 5000: 18500,\n 10000: 30500,\n};\n\n// --- Digest — Substrate ---\n/** Scoring weights by note type when selecting substrate for synthesis. */\nexport const DIGEST_SUBSTRATE_TYPE_WEIGHTS: Record<string, number> = {\n session: 3,\n spore: 3,\n plan: 2,\n artifact: 1,\n team: 1,\n};\n\n// --- LLM reasoning control ---\n/** Reasoning mode for all Myco LLM calls. Suppresses chain-of-thought tokens from reasoning models. */\nexport const LLM_REASONING_MODE = 'off' as const;\n\n// --- Digest — System prompt overhead estimate ---\n"],"mappings":";;;AAQO,IAAM,kBAAkB;AAGxB,SAAS,eAAe,MAAsB;AACnD,SAAO,KAAK,KAAK,KAAK,SAAS,eAAe;AAChD;AAIO,IAAM,wBAAwB;AAI9B,IAAM,uBAAuB;AAE7B,IAAM,4BAA4B;AAElC,IAAM,wBAAwB;AAE9B,IAAM,wBAAwB;AAE9B,IAAM,4BAA4B;AAElC,IAAM,gCAAgC;AAEtC,IAAM,+BAA+B;AAGrC,IAAM,6BAA6B;AACnC,IAAM,gCAAgC;AACtC,IAAM,8BAA8B;AAcpC,IAAM,2BAA2B;AAEjC,IAAM,iCAAiC;AAEvC,IAAM,yBAAyB;AAE/B,IAAM,+BAA+B;AAErC,IAAM,gCAAgC;AAEtC,IAAM,mBAAmB;AAEzB,IAAM,0BAA0B;AAEhC,IAAM,6BAA6B;AAInC,IAAM,0BAA0B,KAAK,KAAK,KAAK;AAI/C,IAAM,6BAA6B,CAAC,KAAK,KAAK,KAAK,KAAK,IAAI;AAI5D,IAAM,+BAA+B;AAIrC,IAAM,kBAAkB;AAIxB,IAAM,4BAA4B;AAQlC,IAAM,gCAAgC;AAEtC,IAAM,uBAAuB;AAI7B,IAAM,4BAA4B;AAElC,IAAM,4BAA4B;AAElC,IAAM,gCAAgC;AAMtC,IAAM,4BAA4B;AAIlC,IAAM,2BAA2B;AAEjC,IAAM,6BAA6B;AAEnC,IAAM,yBAAyB;AAI/B,IAAM,eAAe,CAAC,MAAM,KAAM,KAAM,GAAK;AAK7C,IAAM,0BAAkD;AAAA,EAC7D,MAAM;AAAA,EACN,KAAM;AAAA,EACN,KAAM;AAAA,EACN,KAAO;AACT;AAIO,IAAM,gCAAwD;AAAA,EACnE,SAAS;AAAA,EACT,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AACR;AAIO,IAAM,qBAAqB;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
2
2
  import {
3
3
  AgentRegistry
4
- } from "./chunk-BNIYWCST.js";
4
+ } from "./chunk-EQVQEFOA.js";
5
5
 
6
6
  // src/version.ts
7
7
  import fs from "fs";
@@ -30,4 +30,4 @@ function readVersionFrom(dir) {
30
30
  export {
31
31
  getPluginVersion
32
32
  };
33
- //# sourceMappingURL=chunk-2GJFTIWX.js.map
33
+ //# sourceMappingURL=chunk-TK2ZYIAL.js.map
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
2
2
  import {
3
3
  STDIN_TIMEOUT_MS
4
- } from "./chunk-JBD5KP5G.js";
4
+ } from "./chunk-TDLQBGKA.js";
5
5
 
6
6
  // src/hooks/read-stdin.ts
7
7
  function readStdin() {
@@ -18,4 +18,4 @@ function readStdin() {
18
18
  export {
19
19
  readStdin
20
20
  };
21
- //# sourceMappingURL=chunk-ZCBL5HER.js.map
21
+ //# sourceMappingURL=chunk-XIIVIMFC.js.map