@goondocks/myco 0.4.4 → 0.5.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 +5 -1
- package/dist/{chunk-TK2ZYIAL.js → chunk-7KQB22DP.js} +2 -2
- package/dist/{chunk-TDLQBGKA.js → chunk-B6WVNDA5.js} +10 -2
- package/dist/{chunk-TDLQBGKA.js.map → chunk-B6WVNDA5.js.map} +1 -1
- package/dist/chunk-FIA5NTRH.js +159 -0
- package/dist/chunk-FIA5NTRH.js.map +1 -0
- package/dist/{chunk-5FIIK27E.js → chunk-FIRMTYFH.js} +3 -3
- package/dist/{chunk-6CAKKNGD.js → chunk-HJG7Z6SJ.js} +2 -2
- package/dist/{chunk-3F63SFZZ.js → chunk-HL2S5QZG.js} +8 -4
- package/dist/chunk-HL2S5QZG.js.map +1 -0
- package/dist/{chunk-42R7KVAW.js → chunk-IURC35BF.js} +2 -2
- package/dist/{chunk-OPO47BVS.js → chunk-JI6M2L2W.js} +2 -2
- package/dist/{chunk-PD7LV22R.js → chunk-KYL67SKZ.js} +50 -50
- package/dist/chunk-KYL67SKZ.js.map +1 -0
- package/dist/{chunk-XIIVIMFC.js → chunk-ND4VK6C7.js} +2 -2
- package/dist/{chunk-DKHYIA2V.js → chunk-R6LQT3U7.js} +11 -11
- package/dist/{chunk-OSZRLHIJ.js → chunk-RCV2I4AI.js} +3 -3
- package/dist/{chunk-EQVQEFOA.js → chunk-X6TKHO22.js} +2 -2
- package/dist/{chunk-KDWBZSOB.js → chunk-ZWUFTOG3.js} +4 -4
- package/dist/{cli-WOM4Z2Z4.js → cli-BLYNNKGJ.js} +21 -18
- package/dist/cli-BLYNNKGJ.js.map +1 -0
- package/dist/{client-XCNF6NFT.js → client-5GB4WVXE.js} +5 -5
- package/dist/curate-S4HOYWXA.js +231 -0
- package/dist/curate-S4HOYWXA.js.map +1 -0
- package/dist/{detect-providers-CQSPTW2B.js → detect-providers-BIHYFK5M.js} +3 -3
- package/dist/{digest-WTS6S4XP.js → digest-7NKYXM6G.js} +8 -8
- package/dist/{init-VPLUEULI.js → init-HPQ77WWF.js} +5 -5
- package/dist/{main-OGXH6XWO.js → main-NFQ4II75.js} +36 -14
- package/dist/main-NFQ4II75.js.map +1 -0
- package/dist/{rebuild-Z4YUY6HT.js → rebuild-KQ6G2GZM.js} +4 -4
- package/dist/{reprocess-DMGPZTLC.js → reprocess-ZL4HKTSC.js} +13 -13
- package/dist/{restart-QCQQ55KX.js → restart-FYW662DR.js} +6 -6
- package/dist/{search-ACEFQOUW.js → search-E5JQMTXV.js} +4 -4
- package/dist/{server-BQ3DWKZ6.js → server-TV3D35HZ.js} +30 -9
- package/dist/{server-BQ3DWKZ6.js.map → server-TV3D35HZ.js.map} +1 -1
- package/dist/{session-start-BXRTKS4X.js → session-start-5MFEOVQ5.js} +6 -6
- package/dist/{setup-digest-EJXSQGZ5.js → setup-digest-DZAFIBEF.js} +5 -5
- package/dist/{setup-llm-P3MLWUDR.js → setup-llm-4BZM33YT.js} +5 -5
- 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 +5 -5
- package/dist/src/hooks/user-prompt-submit.js +5 -5
- package/dist/src/mcp/server.js +4 -4
- package/dist/src/prompts/supersession.md +32 -0
- package/dist/{stats-3FAP5FKV.js → stats-ZIIJ2GB3.js} +5 -5
- package/dist/{verify-3FTCOULE.js → verify-RACBFT2P.js} +4 -4
- package/dist/{version-AL67JH7X.js → version-HJTVNPOO.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-3F63SFZZ.js.map +0 -1
- package/dist/chunk-PD7LV22R.js.map +0 -1
- package/dist/cli-WOM4Z2Z4.js.map +0 -1
- package/dist/main-OGXH6XWO.js.map +0 -1
- /package/dist/{chunk-TK2ZYIAL.js.map → chunk-7KQB22DP.js.map} +0 -0
- /package/dist/{chunk-5FIIK27E.js.map → chunk-FIRMTYFH.js.map} +0 -0
- /package/dist/{chunk-6CAKKNGD.js.map → chunk-HJG7Z6SJ.js.map} +0 -0
- /package/dist/{chunk-42R7KVAW.js.map → chunk-IURC35BF.js.map} +0 -0
- /package/dist/{chunk-OPO47BVS.js.map → chunk-JI6M2L2W.js.map} +0 -0
- /package/dist/{chunk-XIIVIMFC.js.map → chunk-ND4VK6C7.js.map} +0 -0
- /package/dist/{chunk-DKHYIA2V.js.map → chunk-R6LQT3U7.js.map} +0 -0
- /package/dist/{chunk-OSZRLHIJ.js.map → chunk-RCV2I4AI.js.map} +0 -0
- /package/dist/{chunk-EQVQEFOA.js.map → chunk-X6TKHO22.js.map} +0 -0
- /package/dist/{chunk-KDWBZSOB.js.map → chunk-ZWUFTOG3.js.map} +0 -0
- /package/dist/{client-XCNF6NFT.js.map → client-5GB4WVXE.js.map} +0 -0
- /package/dist/{detect-providers-CQSPTW2B.js.map → detect-providers-BIHYFK5M.js.map} +0 -0
- /package/dist/{digest-WTS6S4XP.js.map → digest-7NKYXM6G.js.map} +0 -0
- /package/dist/{init-VPLUEULI.js.map → init-HPQ77WWF.js.map} +0 -0
- /package/dist/{rebuild-Z4YUY6HT.js.map → rebuild-KQ6G2GZM.js.map} +0 -0
- /package/dist/{reprocess-DMGPZTLC.js.map → reprocess-ZL4HKTSC.js.map} +0 -0
- /package/dist/{restart-QCQQ55KX.js.map → restart-FYW662DR.js.map} +0 -0
- /package/dist/{search-ACEFQOUW.js.map → search-E5JQMTXV.js.map} +0 -0
- /package/dist/{session-start-BXRTKS4X.js.map → session-start-5MFEOVQ5.js.map} +0 -0
- /package/dist/{setup-digest-EJXSQGZ5.js.map → setup-digest-DZAFIBEF.js.map} +0 -0
- /package/dist/{setup-llm-P3MLWUDR.js.map → setup-llm-4BZM33YT.js.map} +0 -0
- /package/dist/{stats-3FAP5FKV.js.map → stats-ZIIJ2GB3.js.map} +0 -0
- /package/dist/{verify-3FTCOULE.js.map → verify-RACBFT2P.js.map} +0 -0
- /package/dist/{version-AL67JH7X.js.map → version-HJTVNPOO.js.map} +0 -0
package/README.md
CHANGED
|
@@ -50,9 +50,13 @@ myco_context(tier: 3000) → pre-computed project understanding,
|
|
|
50
50
|
|
|
51
51
|
A background daemon reads your agent's conversation transcript after each turn — the full dialogue including prompts, AI responses, tool calls, and screenshots. Observations called **spores** (decisions, gotchas, discoveries, trade-offs, bug fixes) are extracted automatically via a local LLM and written as linked vault notes.
|
|
52
52
|
|
|
53
|
+
### Curate
|
|
54
|
+
|
|
55
|
+
As a project evolves, older observations become stale. Myco automatically detects and supersedes outdated spores when new ones are created — using vector similarity to find candidates and an LLM to judge which are truly replaced vs. merely related. Superseded spores are preserved with lineage metadata (never deleted), but filtered from search results and digest synthesis. Run `myco curate` for vault-wide cleanup, or let it happen automatically on every spore write.
|
|
56
|
+
|
|
53
57
|
### Digest
|
|
54
58
|
|
|
55
|
-
A **continuous reasoning engine** runs inside the daemon, periodically synthesizing all accumulated knowledge into tiered context extracts. These pre-computed summaries give agents an instant, rich understanding of the project at session start — no searching required. Four tiers serve different needs: executive briefing (1.5K tokens), team standup (3K), deep onboarding (5K), and institutional knowledge (10K).
|
|
59
|
+
A **continuous reasoning engine** runs inside the daemon, periodically synthesizing all accumulated knowledge into tiered context extracts. These pre-computed summaries give agents an instant, rich understanding of the project at session start — no searching required. Four tiers serve different needs: executive briefing (1.5K tokens), team standup (3K), deep onboarding (5K), and institutional knowledge (10K). Run `myco digest --tier 3000` to reprocess a specific tier from scratch, or `myco digest --full` for a complete rebuild.
|
|
56
60
|
|
|
57
61
|
### Index
|
|
58
62
|
|
|
@@ -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-
|
|
4
|
+
} from "./chunk-X6TKHO22.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-
|
|
33
|
+
//# sourceMappingURL=chunk-7KQB22DP.js.map
|
|
@@ -53,6 +53,10 @@ var DIGEST_SUBSTRATE_TYPE_WEIGHTS = {
|
|
|
53
53
|
team: 1
|
|
54
54
|
};
|
|
55
55
|
var LLM_REASONING_MODE = "off";
|
|
56
|
+
var SUPERSESSION_CANDIDATE_LIMIT = 5;
|
|
57
|
+
var SUPERSESSION_VECTOR_FETCH_LIMIT = 20;
|
|
58
|
+
var SUPERSESSION_MAX_TOKENS = 256;
|
|
59
|
+
var CURATION_CLUSTER_SIMILARITY = 0.75;
|
|
56
60
|
|
|
57
61
|
export {
|
|
58
62
|
CHARS_PER_TOKEN,
|
|
@@ -93,6 +97,10 @@ export {
|
|
|
93
97
|
DIGEST_TIERS,
|
|
94
98
|
DIGEST_TIER_MIN_CONTEXT,
|
|
95
99
|
DIGEST_SUBSTRATE_TYPE_WEIGHTS,
|
|
96
|
-
LLM_REASONING_MODE
|
|
100
|
+
LLM_REASONING_MODE,
|
|
101
|
+
SUPERSESSION_CANDIDATE_LIMIT,
|
|
102
|
+
SUPERSESSION_VECTOR_FETCH_LIMIT,
|
|
103
|
+
SUPERSESSION_MAX_TOKENS,
|
|
104
|
+
CURATION_CLUSTER_SIMILARITY
|
|
97
105
|
};
|
|
98
|
-
//# sourceMappingURL=chunk-
|
|
106
|
+
//# sourceMappingURL=chunk-B6WVNDA5.js.map
|
|
@@ -1 +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
|
+
{"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\n// --- Vault curation ---\n/** Max candidate spores after post-filtering for supersession check. */\nexport const SUPERSESSION_CANDIDATE_LIMIT = 5;\n\n/** Over-fetch from vector index before post-filtering by status/type. */\nexport const SUPERSESSION_VECTOR_FETCH_LIMIT = 20;\n\n/** Max output tokens for supersession LLM evaluation. */\nexport const SUPERSESSION_MAX_TOKENS = 256;\n\n/** Similarity threshold for clustering related spores in batch curation. */\nexport const CURATION_CLUSTER_SIMILARITY = 0.75;\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;AAM3B,IAAM,+BAA+B;AAGrC,IAAM,kCAAkC;AAGxC,IAAM,0BAA0B;AAGhC,IAAM,8BAA8B;","names":[]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
indexNote
|
|
4
|
+
} from "./chunk-JJL6AMDA.js";
|
|
5
|
+
import {
|
|
6
|
+
generateEmbedding
|
|
7
|
+
} from "./chunk-RGVBGTD6.js";
|
|
8
|
+
import {
|
|
9
|
+
loadPrompt,
|
|
10
|
+
stripReasoningTokens
|
|
11
|
+
} from "./chunk-KYL67SKZ.js";
|
|
12
|
+
import {
|
|
13
|
+
external_exports,
|
|
14
|
+
require_dist
|
|
15
|
+
} from "./chunk-6UJWI4IW.js";
|
|
16
|
+
import {
|
|
17
|
+
EMBEDDING_INPUT_LIMIT,
|
|
18
|
+
LLM_REASONING_MODE,
|
|
19
|
+
SUPERSESSION_CANDIDATE_LIMIT,
|
|
20
|
+
SUPERSESSION_MAX_TOKENS,
|
|
21
|
+
SUPERSESSION_VECTOR_FETCH_LIMIT
|
|
22
|
+
} from "./chunk-B6WVNDA5.js";
|
|
23
|
+
import {
|
|
24
|
+
__toESM
|
|
25
|
+
} from "./chunk-PZUWP5VK.js";
|
|
26
|
+
|
|
27
|
+
// src/vault/curation.ts
|
|
28
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
29
|
+
import fs from "fs";
|
|
30
|
+
import path from "path";
|
|
31
|
+
var supersededIdsSchema = external_exports.array(external_exports.string());
|
|
32
|
+
var SUPERSESSION_NOTICE_MARKER = "Superseded by::";
|
|
33
|
+
function isActiveSpore(frontmatter) {
|
|
34
|
+
const status = frontmatter.status;
|
|
35
|
+
return !status || status === "active";
|
|
36
|
+
}
|
|
37
|
+
function supersedeSpore(targetId, newSporeId, targetPath, deps) {
|
|
38
|
+
const fullPath = path.join(deps.vaultDir, targetPath);
|
|
39
|
+
let fileContent;
|
|
40
|
+
try {
|
|
41
|
+
fileContent = fs.readFileSync(fullPath, "utf-8");
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const fmMatch = fileContent.match(/^---\n([\s\S]*?)\n---/);
|
|
46
|
+
if (!fmMatch) return false;
|
|
47
|
+
const parsed = import_yaml.default.parse(fmMatch[1]);
|
|
48
|
+
parsed.status = "superseded";
|
|
49
|
+
parsed.superseded_by = newSporeId;
|
|
50
|
+
const fmYaml = import_yaml.default.stringify(parsed, { defaultStringType: "QUOTE_DOUBLE", defaultKeyType: "PLAIN" }).trim();
|
|
51
|
+
let body = fileContent.slice(fmMatch[0].length);
|
|
52
|
+
if (!body.includes(SUPERSESSION_NOTICE_MARKER)) {
|
|
53
|
+
const notice = `
|
|
54
|
+
|
|
55
|
+
> [!warning] Superseded
|
|
56
|
+
> This observation has been superseded.
|
|
57
|
+
|
|
58
|
+
${SUPERSESSION_NOTICE_MARKER} [[${newSporeId}]]`;
|
|
59
|
+
body = body.trimEnd() + notice + "\n";
|
|
60
|
+
}
|
|
61
|
+
const tmp = `${fullPath}.tmp`;
|
|
62
|
+
fs.writeFileSync(tmp, `---
|
|
63
|
+
${fmYaml}
|
|
64
|
+
---${body}`, "utf-8");
|
|
65
|
+
fs.renameSync(tmp, fullPath);
|
|
66
|
+
indexNote(deps.index, deps.vaultDir, targetPath);
|
|
67
|
+
deps.vectorIndex?.delete(targetId);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
async function checkSupersession(newSporeId, deps) {
|
|
71
|
+
const { index, vectorIndex, embeddingProvider, llmProvider, vaultDir, log } = deps;
|
|
72
|
+
if (!vectorIndex || !llmProvider) {
|
|
73
|
+
log?.("debug", "checkSupersession: skipped \u2014 vectorIndex or llmProvider unavailable", { newSporeId });
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
const newSporeResults = index.queryByIds([newSporeId]);
|
|
77
|
+
if (newSporeResults.length === 0) {
|
|
78
|
+
log?.("warn", "checkSupersession: new spore not found in index", { newSporeId });
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const newSpore = newSporeResults[0];
|
|
82
|
+
const observationType = newSpore.frontmatter["observation_type"];
|
|
83
|
+
const embeddingText = newSpore.content.slice(0, EMBEDDING_INPUT_LIMIT);
|
|
84
|
+
const embeddingResult = await generateEmbedding(embeddingProvider, embeddingText);
|
|
85
|
+
const vectorResults = vectorIndex.search(embeddingResult.embedding, {
|
|
86
|
+
type: "spore",
|
|
87
|
+
limit: SUPERSESSION_VECTOR_FETCH_LIMIT
|
|
88
|
+
});
|
|
89
|
+
if (vectorResults.length === 0) {
|
|
90
|
+
log?.("debug", "checkSupersession: no vector results", { newSporeId });
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const candidateIds = vectorResults.map((r) => r.id);
|
|
94
|
+
const candidateNotes = index.queryByIds(candidateIds);
|
|
95
|
+
const filtered = candidateNotes.filter((note) => {
|
|
96
|
+
if (note.id === newSporeId) return false;
|
|
97
|
+
if (!isActiveSpore(note.frontmatter)) return false;
|
|
98
|
+
if (observationType && note.frontmatter["observation_type"] !== observationType) return false;
|
|
99
|
+
return true;
|
|
100
|
+
}).slice(0, SUPERSESSION_CANDIDATE_LIMIT);
|
|
101
|
+
if (filtered.length === 0) {
|
|
102
|
+
log?.("debug", "checkSupersession: no candidates after filtering", { newSporeId, observationType });
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const template = loadPrompt("supersession");
|
|
106
|
+
const newSporeText = `[${newSpore.id}] ${newSpore.title}
|
|
107
|
+
${newSpore.content}`;
|
|
108
|
+
const candidatesText = filtered.map((c) => `[${c.id}] ${c.title}
|
|
109
|
+
${c.content}`).join("\n\n");
|
|
110
|
+
const prompt = template.replace("{{new_spore}}", newSporeText).replace("{{candidates}}", candidatesText);
|
|
111
|
+
let responseText;
|
|
112
|
+
try {
|
|
113
|
+
const response = await llmProvider.summarize(prompt, {
|
|
114
|
+
maxTokens: SUPERSESSION_MAX_TOKENS,
|
|
115
|
+
reasoning: LLM_REASONING_MODE
|
|
116
|
+
});
|
|
117
|
+
responseText = stripReasoningTokens(response.text);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
log?.("warn", "checkSupersession: LLM call failed", { newSporeId, error: String(err) });
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
let rawIds;
|
|
123
|
+
try {
|
|
124
|
+
rawIds = JSON.parse(responseText);
|
|
125
|
+
} catch {
|
|
126
|
+
log?.("warn", "checkSupersession: failed to parse LLM response", { newSporeId, responseText });
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
const parsed = supersededIdsSchema.safeParse(rawIds);
|
|
130
|
+
if (!parsed.success) {
|
|
131
|
+
log?.("warn", "checkSupersession: LLM response failed schema validation", { newSporeId });
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
const candidateMap = new Map(filtered.map((c) => [c.id, c]));
|
|
135
|
+
const validIds = parsed.data.filter((id) => candidateMap.has(id));
|
|
136
|
+
if (validIds.length === 0) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const supersededIds = [];
|
|
140
|
+
for (const id of validIds) {
|
|
141
|
+
const candidate = candidateMap.get(id);
|
|
142
|
+
const wrote = supersedeSpore(id, newSporeId, candidate.path, { index, vectorIndex, vaultDir });
|
|
143
|
+
if (!wrote) {
|
|
144
|
+
log?.("warn", "checkSupersession: file not found for candidate, skipping", { id, path: candidate.path });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
supersededIds.push(id);
|
|
148
|
+
log?.("info", "checkSupersession: marked superseded", { supersededId: id, newSporeId });
|
|
149
|
+
}
|
|
150
|
+
return supersededIds;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
supersededIdsSchema,
|
|
155
|
+
isActiveSpore,
|
|
156
|
+
supersedeSpore,
|
|
157
|
+
checkSupersession
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=chunk-FIA5NTRH.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 } 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 = `[${newSpore.id}] ${newSpore.title}\\n${newSpore.content}`;\n const candidatesText = filtered\n .map((c) => `[${c.id}] ${c.title}\\n${c.content}`)\n .join('\\n\\n');\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"]}
|
|
@@ -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-
|
|
5
|
+
} from "./chunk-JI6M2L2W.js";
|
|
6
6
|
import {
|
|
7
7
|
AgentRegistry
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-X6TKHO22.js";
|
|
9
9
|
|
|
10
10
|
// src/cli/shared.ts
|
|
11
11
|
import fs from "fs";
|
|
@@ -116,4 +116,4 @@ export {
|
|
|
116
116
|
VAULT_GITIGNORE,
|
|
117
117
|
configureVaultEnv
|
|
118
118
|
};
|
|
119
|
-
//# sourceMappingURL=chunk-
|
|
119
|
+
//# sourceMappingURL=chunk-FIRMTYFH.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
|
AgentRegistry
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-X6TKHO22.js";
|
|
5
5
|
|
|
6
6
|
// src/native-deps.ts
|
|
7
7
|
import { execFileSync } from "child_process";
|
|
@@ -53,4 +53,4 @@ function ensureNativeDeps() {
|
|
|
53
53
|
export {
|
|
54
54
|
ensureNativeDeps
|
|
55
55
|
};
|
|
56
|
-
//# sourceMappingURL=chunk-
|
|
56
|
+
//# sourceMappingURL=chunk-HJG7Z6SJ.js.map
|
|
@@ -2,7 +2,7 @@ import { createRequire as __cr } from 'node:module'; const require = __cr(import
|
|
|
2
2
|
import {
|
|
3
3
|
loadPrompt,
|
|
4
4
|
stripReasoningTokens
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KYL67SKZ.js";
|
|
6
6
|
import {
|
|
7
7
|
stripFrontmatter
|
|
8
8
|
} from "./chunk-MIU3DKLN.js";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
DIGEST_TIER_MIN_CONTEXT,
|
|
17
17
|
LLM_REASONING_MODE,
|
|
18
18
|
estimateTokens
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-B6WVNDA5.js";
|
|
20
20
|
import {
|
|
21
21
|
__toESM
|
|
22
22
|
} from "./chunk-PZUWP5VK.js";
|
|
@@ -52,7 +52,11 @@ var DigestEngine = class {
|
|
|
52
52
|
discoverSubstrate(lastCycleTimestamp) {
|
|
53
53
|
const maxNotes = this.config.digest.substrate.max_notes_per_cycle;
|
|
54
54
|
const notes = lastCycleTimestamp ? this.index.query({ updatedSince: lastCycleTimestamp, limit: maxNotes }) : this.index.query({ limit: maxNotes });
|
|
55
|
-
const filtered = notes.filter((n) => n.type !== EXTRACT_TYPE)
|
|
55
|
+
const filtered = notes.filter((n) => n.type !== EXTRACT_TYPE).filter((n) => {
|
|
56
|
+
if (n.type !== "spore") return true;
|
|
57
|
+
const status = n.frontmatter.status;
|
|
58
|
+
return !status || status === "active";
|
|
59
|
+
});
|
|
56
60
|
filtered.sort((a, b) => {
|
|
57
61
|
const weightA = DIGEST_SUBSTRATE_TYPE_WEIGHTS[a.type] ?? 0;
|
|
58
62
|
const weightB = DIGEST_SUBSTRATE_TYPE_WEIGHTS[b.type] ?? 0;
|
|
@@ -378,4 +382,4 @@ export {
|
|
|
378
382
|
DigestEngine,
|
|
379
383
|
Metabolism
|
|
380
384
|
};
|
|
381
|
-
//# sourceMappingURL=chunk-
|
|
385
|
+
//# sourceMappingURL=chunk-HL2S5QZG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/digest.ts"],"sourcesContent":["/**\n * DigestEngine — synthesizes vault knowledge into tiered context extracts.\n * Metabolism — adaptive timer that throttles digest cycles based on activity.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport crypto from 'node:crypto';\nimport YAML from 'yaml';\n\nimport type { MycoIndex, IndexedNote } from '@myco/index/sqlite.js';\nimport type { LlmProvider, LlmRequestOptions } from '@myco/intelligence/llm.js';\nimport type { MycoConfig } from '@myco/config/schema.js';\nimport { loadPrompt } from '@myco/prompts/index.js';\nimport { stripReasoningTokens } from '@myco/intelligence/response.js';\nimport { stripFrontmatter } from '@myco/vault/frontmatter.js';\nimport {\n estimateTokens,\n CHARS_PER_TOKEN,\n DIGEST_TIER_MIN_CONTEXT,\n DIGEST_SUBSTRATE_TYPE_WEIGHTS,\n DIGEST_LLM_REQUEST_TIMEOUT_MS,\n LLM_REASONING_MODE,\n} from '@myco/constants.js';\n\n// --- Interfaces ---\n\nexport interface DigestCycleResult {\n cycleId: string;\n timestamp: string;\n substrate: {\n sessions: string[];\n spores: string[];\n plans: string[];\n artifacts: string[];\n team: string[];\n };\n tiersGenerated: number[];\n model: string;\n durationMs: number;\n tokensUsed: number;\n}\n\n/** Simple log function signature for digest progress reporting. */\nexport type DigestLogFn = (level: 'debug' | 'info' | 'warn', message: string, data?: Record<string, unknown>) => void;\n\nexport interface DigestCycleOptions {\n /** Process all substrate regardless of last cycle timestamp. */\n fullReprocess?: boolean;\n /** Only generate these tiers (default: all eligible). */\n tiers?: number[];\n /** Skip previous extract — start from clean slate. */\n cleanSlate?: boolean;\n}\n\nexport interface DigestEngineConfig {\n vaultDir: string;\n index: MycoIndex;\n llmProvider: LlmProvider;\n config: MycoConfig;\n log?: DigestLogFn;\n}\n\n// --- Constants ---\n\n/** Token overhead estimate for previous extract section wrapper. */\nconst PREVIOUS_EXTRACT_OVERHEAD_TOKENS = 50;\n\n/** Safety margin for context window — our CHARS_PER_TOKEN=4 heuristic significantly\n * underestimates real token counts (observed ~3.2 chars/token for mixed content).\n * 0.70 provides a safe buffer: 32K * 0.70 = 22.4K usable tokens. */\nconst CONTEXT_SAFETY_MARGIN = 0.70;\n\n/** Types that are digest output — excluded from substrate to avoid self-digestion. */\nconst EXTRACT_TYPE = 'extract';\n\n// --- DigestEngine ---\n\nexport class DigestEngine {\n private vaultDir: string;\n private index: MycoIndex;\n private llm: LlmProvider;\n private config: MycoConfig;\n private log: DigestLogFn;\n private lastCycleTimestampCache: string | null | undefined = undefined;\n private cycleInProgress = false;\n\n constructor(engineConfig: DigestEngineConfig) {\n this.vaultDir = engineConfig.vaultDir;\n this.index = engineConfig.index;\n this.llm = engineConfig.llmProvider;\n this.config = engineConfig.config;\n this.log = engineConfig.log ?? (() => {});\n }\n\n /**\n * Query index for recent vault notes to feed into the digest.\n * Filters out extract notes (our own output) and caps at max_notes_per_cycle.\n */\n discoverSubstrate(lastCycleTimestamp: string | null): IndexedNote[] {\n const maxNotes = this.config.digest.substrate.max_notes_per_cycle;\n\n const notes = lastCycleTimestamp\n ? this.index.query({ updatedSince: lastCycleTimestamp, limit: maxNotes })\n : this.index.query({ limit: maxNotes });\n\n // Guard against self-digestion: extract files are not currently indexed,\n // but this filter prevents feedback loops if they ever are (e.g., via rebuild)\n const filtered = notes\n .filter((n) => n.type !== EXTRACT_TYPE)\n .filter((n) => {\n if (n.type !== 'spore') return true;\n const status = n.frontmatter.status as string | undefined;\n return !status || status === 'active';\n });\n\n // Sort by type weight (descending) then by recency (descending)\n filtered.sort((a, b) => {\n const weightA = DIGEST_SUBSTRATE_TYPE_WEIGHTS[a.type] ?? 0;\n const weightB = DIGEST_SUBSTRATE_TYPE_WEIGHTS[b.type] ?? 0;\n if (weightB !== weightA) return weightB - weightA;\n // More recent first — created is ISO string, lexicographic sort works\n return b.created.localeCompare(a.created);\n });\n\n return filtered.slice(0, maxNotes);\n }\n\n /**\n * Filter configured tiers by the context window available.\n * Only tiers whose minimum context requirement is met are eligible.\n */\n getEligibleTiers(): number[] {\n const contextWindow = this.config.digest.intelligence.context_window;\n return this.config.digest.tiers.filter((tier) => {\n const minContext = DIGEST_TIER_MIN_CONTEXT[tier];\n return minContext !== undefined && minContext <= contextWindow;\n });\n }\n\n /**\n * Format notes compactly for inclusion in the digest prompt.\n * Stops adding notes once the token budget is exceeded.\n */\n formatSubstrate(notes: IndexedNote[], tokenBudget: number): string {\n const charBudget = tokenBudget * CHARS_PER_TOKEN;\n const parts: string[] = [];\n let usedChars = 0;\n\n for (const note of notes) {\n const entry = `### [${note.type}] ${note.id} — \"${note.title}\"\\n${note.content}`;\n if (usedChars + entry.length > charBudget && parts.length > 0) break;\n parts.push(entry);\n usedChars += entry.length;\n }\n\n return parts.join('\\n\\n');\n }\n\n /**\n * Read a previously generated extract for a given tier.\n * Returns the body (stripped of YAML frontmatter), or null if not found.\n */\n readPreviousExtract(tier: number): string | null {\n const extractPath = path.join(this.vaultDir, 'digest', `extract-${tier}.md`);\n let content: string;\n try {\n content = fs.readFileSync(extractPath, 'utf-8');\n } catch {\n return null;\n }\n\n return stripFrontmatter(content).body;\n }\n\n /**\n * Write a digest extract to the vault with YAML frontmatter.\n * Uses atomic write pattern (temp file + rename).\n */\n writeExtract(\n tier: number,\n body: string,\n cycleId: string,\n model: string,\n substrateCount: number,\n ): void {\n const digestDir = path.join(this.vaultDir, 'digest');\n fs.mkdirSync(digestDir, { recursive: true });\n\n const frontmatter: Record<string, unknown> = {\n type: EXTRACT_TYPE,\n tier,\n generated: new Date().toISOString(),\n cycle_id: cycleId,\n substrate_count: substrateCount,\n model,\n };\n\n const fmYaml = YAML.stringify(frontmatter, {\n defaultStringType: 'QUOTE_DOUBLE',\n defaultKeyType: 'PLAIN',\n }).trim();\n const file = `---\\n${fmYaml}\\n---\\n\\n${body}\\n`;\n\n const fullPath = path.join(digestDir, `extract-${tier}.md`);\n const tmpPath = `${fullPath}.tmp`;\n fs.writeFileSync(tmpPath, file, 'utf-8');\n fs.renameSync(tmpPath, fullPath);\n }\n\n /**\n * Append a digest cycle result as a JSON line to trace.jsonl.\n */\n appendTrace(record: DigestCycleResult): void {\n const digestDir = path.join(this.vaultDir, 'digest');\n fs.mkdirSync(digestDir, { recursive: true });\n const tracePath = path.join(digestDir, 'trace.jsonl');\n fs.appendFileSync(tracePath, JSON.stringify(record) + '\\n', 'utf-8');\n this.lastCycleTimestampCache = record.timestamp;\n }\n\n /**\n * Read the last cycle timestamp from trace.jsonl.\n * Cached in memory after first read — subsequent calls are O(1).\n */\n getLastCycleTimestamp(): string | null {\n if (this.lastCycleTimestampCache !== undefined) return this.lastCycleTimestampCache;\n\n const tracePath = path.join(this.vaultDir, 'digest', 'trace.jsonl');\n let content: string;\n try {\n content = fs.readFileSync(tracePath, 'utf-8').trim();\n } catch {\n this.lastCycleTimestampCache = null;\n return null;\n }\n\n if (!content) {\n this.lastCycleTimestampCache = null;\n return null;\n }\n\n const lines = content.split('\\n');\n const lastLine = lines[lines.length - 1];\n try {\n const record = JSON.parse(lastLine) as DigestCycleResult;\n this.lastCycleTimestampCache = record.timestamp;\n return record.timestamp;\n } catch {\n this.lastCycleTimestampCache = null;\n return null;\n }\n }\n\n /**\n * Run a full digest cycle: discover substrate, generate extracts for each tier.\n * Returns the cycle result, or null if no substrate was found.\n */\n async runCycle(opts?: DigestCycleOptions): Promise<DigestCycleResult | null> {\n if (this.cycleInProgress) {\n this.log('debug', 'Cycle already in progress — skipping');\n return null;\n }\n this.cycleInProgress = true;\n\n try {\n return await this.runCycleInternal(opts);\n } finally {\n this.cycleInProgress = false;\n }\n }\n\n private async runCycleInternal(opts?: DigestCycleOptions): Promise<DigestCycleResult | null> {\n // Ensure model is loaded with correct settings every cycle.\n // LM Studio's idle TTL can evict our instance between cycles — without\n // re-running ensureLoaded, the auto-reloaded instance would use LM Studio's\n // UI defaults (wrong KV cache setting). This is fast (~26ms) when the\n // instance is still alive (just a getLoadedInstances check).\n if (this.llm.ensureLoaded) {\n const { context_window: contextWindow, gpu_kv_cache: gpuKvCache } = this.config.digest.intelligence;\n this.log('debug', 'Verifying digest model', { contextWindow, gpuKvCache });\n await this.llm.ensureLoaded(contextWindow, gpuKvCache);\n }\n\n const startTime = Date.now();\n const fullReprocess = opts?.fullReprocess ?? false;\n const lastTimestamp = fullReprocess ? null : this.getLastCycleTimestamp();\n const substrate = this.discoverSubstrate(lastTimestamp);\n\n this.log('debug', 'Discovering substrate', { lastTimestamp: lastTimestamp ?? 'full reprocess', substrateCount: substrate.length });\n if (substrate.length === 0) {\n this.log('debug', 'No substrate found — skipping cycle');\n return null;\n }\n\n this.log('info', `Starting digest cycle`, { substrateCount: substrate.length, fullReprocess });\n const cycleId = crypto.randomUUID();\n const allEligible = this.getEligibleTiers();\n const eligibleTiers = opts?.tiers\n ? allEligible.filter((t) => opts.tiers!.includes(t))\n : allEligible;\n this.log('debug', `Eligible tiers: [${eligibleTiers.join(', ')}]`);\n const tiersGenerated: number[] = [];\n let totalTokensUsed = 0;\n let model = '';\n\n // Categorize substrate by type for the result\n const typeToKey: Record<string, keyof DigestCycleResult['substrate']> = {\n session: 'sessions',\n spore: 'spores',\n plan: 'plans',\n artifact: 'artifacts',\n 'team-member': 'team',\n };\n const substrateIndex: DigestCycleResult['substrate'] = {\n sessions: [],\n spores: [],\n plans: [],\n artifacts: [],\n team: [],\n };\n for (const note of substrate) {\n const key = typeToKey[note.type];\n if (key) {\n substrateIndex[key].push(note.id);\n }\n }\n\n // Record the cycle timestamp NOW, before tier processing. This ensures the\n // timestamp advances even if LLM calls fail, preventing the same substrate\n // from being rediscovered on every subsequent timer fire.\n const cycleTimestamp = new Date().toISOString();\n\n const systemPrompt = loadPrompt('digest-system');\n\n for (const tier of eligibleTiers) {\n const tierPrompt = loadPrompt(`digest-${tier}`);\n const previousExtract = opts?.cleanSlate ? null : this.readPreviousExtract(tier);\n\n // Calculate token budget for substrate:\n // (context_window * safety_margin) - output - system_prompt - tier_prompt - previous_extract\n const contextWindow = this.config.digest.intelligence.context_window;\n const systemPromptTokens = estimateTokens(systemPrompt);\n const tierPromptTokens = estimateTokens(tierPrompt);\n const previousExtractTokens = previousExtract\n ? estimateTokens(previousExtract) + PREVIOUS_EXTRACT_OVERHEAD_TOKENS\n : 0;\n const availableTokens = Math.floor(contextWindow * CONTEXT_SAFETY_MARGIN);\n const substrateBudget = availableTokens - tier - systemPromptTokens - tierPromptTokens - previousExtractTokens;\n\n if (substrateBudget <= 0) continue;\n\n const formattedSubstrate = this.formatSubstrate(substrate, substrateBudget);\n\n // Build user prompt (system prompt sent separately via LlmRequestOptions)\n const promptParts = [tierPrompt];\n\n if (previousExtract) {\n promptParts.push('', '## Previous Synthesis', '', previousExtract);\n }\n\n promptParts.push('', '## New Substrate', '', formattedSubstrate);\n promptParts.push(\n '',\n '---',\n 'Produce your updated synthesis now. Stay within the token budget specified above.',\n );\n\n const userPrompt = promptParts.join('\\n');\n const promptTokens = estimateTokens(systemPrompt + userPrompt);\n this.log('debug', `Tier ${tier}: sending LLM request`, { promptTokens, maxTokens: tier, substrateBudget });\n\n try {\n const tierStart = Date.now();\n const digestConfig = this.config.digest.intelligence;\n const opts: LlmRequestOptions = {\n maxTokens: tier,\n timeoutMs: DIGEST_LLM_REQUEST_TIMEOUT_MS,\n contextLength: contextWindow,\n reasoning: LLM_REASONING_MODE,\n systemPrompt,\n keepAlive: digestConfig.keep_alive ?? undefined,\n };\n const response = await this.llm.summarize(userPrompt, opts);\n const tierDuration = Date.now() - tierStart;\n\n // Strip reasoning tokens if present (some models output chain-of-thought)\n const extractText = stripReasoningTokens(response.text);\n model = response.model;\n const responseTokens = estimateTokens(extractText);\n totalTokensUsed += promptTokens + responseTokens;\n\n this.log('info', `Tier ${tier}: completed`, { durationMs: tierDuration, responseTokens, model: response.model });\n this.writeExtract(tier, extractText, cycleId, response.model, substrate.length);\n tiersGenerated.push(tier);\n } catch (err) {\n this.log('warn', `Tier ${tier}: failed`, { error: (err as Error).message });\n }\n }\n\n const result: DigestCycleResult = {\n cycleId,\n timestamp: cycleTimestamp,\n substrate: substrateIndex,\n tiersGenerated,\n model,\n durationMs: Date.now() - startTime,\n tokensUsed: totalTokensUsed,\n };\n\n this.appendTrace(result);\n return result;\n }\n}\n\n// --- Metabolism (Adaptive Timer) ---\n\nexport type MetabolismState = 'active' | 'cooling' | 'dormant';\n\n/** Milliseconds per second for config conversion. */\nconst MS_PER_SECOND = 1000;\n\nexport class Metabolism {\n state: MetabolismState = 'active';\n currentIntervalMs: number;\n\n private cooldownStep = 0;\n private lastSubstrateTime: number;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private activeIntervalMs: number;\n private cooldownIntervalsMs: number[];\n private dormancyThresholdMs: number;\n\n constructor(config: MycoConfig['digest']['metabolism']) {\n this.activeIntervalMs = config.active_interval * MS_PER_SECOND;\n this.cooldownIntervalsMs = config.cooldown_intervals.map((s) => s * MS_PER_SECOND);\n this.dormancyThresholdMs = config.dormancy_threshold * MS_PER_SECOND;\n this.currentIntervalMs = this.activeIntervalMs;\n this.lastSubstrateTime = Date.now();\n }\n\n /** Reset to active state when new substrate is found. */\n onSubstrateFound(): void {\n this.state = 'active';\n this.cooldownStep = 0;\n this.currentIntervalMs = this.activeIntervalMs;\n this.lastSubstrateTime = Date.now();\n }\n\n /** Advance cooldown when a cycle finds no new substrate. */\n onEmptyCycle(): void {\n if (this.state === 'dormant') return;\n\n this.state = 'cooling';\n if (this.cooldownStep < this.cooldownIntervalsMs.length) {\n this.currentIntervalMs = this.cooldownIntervalsMs[this.cooldownStep];\n this.cooldownStep++;\n }\n\n this.checkDormancy();\n }\n\n /** Enter dormant state if enough time has elapsed since last substrate. */\n checkDormancy(): void {\n const elapsed = Date.now() - this.lastSubstrateTime;\n if (elapsed >= this.dormancyThresholdMs) {\n this.state = 'dormant';\n // Keep the last cooldown interval as the dormant polling rate\n }\n }\n\n /** Return to active from any state, resetting timers and rescheduling immediately. */\n activate(): void {\n this.onSubstrateFound();\n // Reschedule with the new active interval — without this, the old\n // (possibly dormant) timer continues ticking at the wrong rate\n if (this.callback) {\n this.reschedule();\n }\n }\n\n /** Set lastSubstrateTime explicitly (for testing). */\n markLastSubstrate(time: number): void {\n this.lastSubstrateTime = time;\n }\n\n /** Begin scheduling digest cycles with adaptive intervals. */\n start(callback: () => Promise<void>): void {\n this.callback = callback;\n this.reschedule();\n }\n\n /** Stop the timer. */\n stop(): void {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n }\n\n private callback: (() => Promise<void>) | null = null;\n\n private reschedule(): void {\n this.stop();\n if (!this.callback) return;\n const cb = this.callback;\n const schedule = (): void => {\n this.timer = setTimeout(async () => {\n await cb();\n schedule();\n }, this.currentIntervalMs);\n this.timer.unref();\n };\n schedule();\n }\n}\n"],"mappings":"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","names":["YAML","opts"]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-MIU3DKLN.js";
|
|
5
5
|
import {
|
|
6
6
|
DIGEST_TIERS
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-B6WVNDA5.js";
|
|
8
8
|
|
|
9
9
|
// src/mcp/tools/context.ts
|
|
10
10
|
import fs from "fs";
|
|
@@ -46,4 +46,4 @@ function handleMycoContext(vaultDir, input) {
|
|
|
46
46
|
export {
|
|
47
47
|
handleMycoContext
|
|
48
48
|
};
|
|
49
|
-
//# sourceMappingURL=chunk-
|
|
49
|
+
//# sourceMappingURL=chunk-IURC35BF.js.map
|
|
@@ -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-B6WVNDA5.js";
|
|
7
7
|
|
|
8
8
|
// src/intelligence/ollama.ts
|
|
9
9
|
var ENDPOINT_GENERATE = "/api/generate";
|
|
@@ -270,4 +270,4 @@ export {
|
|
|
270
270
|
OllamaBackend,
|
|
271
271
|
LmStudioBackend
|
|
272
272
|
};
|
|
273
|
-
//# sourceMappingURL=chunk-
|
|
273
|
+
//# sourceMappingURL=chunk-JI6M2L2W.js.map
|
|
@@ -4,7 +4,51 @@ import {
|
|
|
4
4
|
} from "./chunk-2AMAOSRF.js";
|
|
5
5
|
import {
|
|
6
6
|
CANDIDATE_CONTENT_PREVIEW
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-B6WVNDA5.js";
|
|
8
|
+
|
|
9
|
+
// src/intelligence/response.ts
|
|
10
|
+
var REASONING_PATTERNS = [
|
|
11
|
+
// <think>...</think>answer (DeepSeek, Qwen, GLM, many others)
|
|
12
|
+
/<think>[\s\S]*?<\/think>\s*/gi,
|
|
13
|
+
// Implicit opening: reasoning...</think>answer (GLM-4.7 observed)
|
|
14
|
+
/^[\s\S]*?<\/think>\s*/i,
|
|
15
|
+
// <reasoning>...</reasoning>answer
|
|
16
|
+
/<reasoning>[\s\S]*?<\/reasoning>\s*/gi,
|
|
17
|
+
// <|thinking|>...<|/thinking|>answer
|
|
18
|
+
/<\|thinking\|>[\s\S]*?<\|\/thinking\|>\s*/gi,
|
|
19
|
+
// Plain-text "Thinking Process:" block followed by actual content
|
|
20
|
+
// (Qwen 3.5 via LM Studio without native thinking mode)
|
|
21
|
+
// Matches from "Thinking Process:" up to the last numbered step, then the synthesis follows
|
|
22
|
+
/^Thinking Process:[\s\S]*?(?=\n(?:## |# |\*\*[A-Z]))/i
|
|
23
|
+
];
|
|
24
|
+
function stripReasoningTokens(text) {
|
|
25
|
+
if (!text) return text;
|
|
26
|
+
for (const pattern of REASONING_PATTERNS) {
|
|
27
|
+
const stripped = text.replace(pattern, "").trim();
|
|
28
|
+
if (stripped && stripped !== text.trim()) {
|
|
29
|
+
return stripped;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return text;
|
|
33
|
+
}
|
|
34
|
+
function extractJson(text) {
|
|
35
|
+
const cleaned = stripReasoningTokens(text);
|
|
36
|
+
const fenceMatch = cleaned.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
37
|
+
if (fenceMatch) {
|
|
38
|
+
return JSON.parse(fenceMatch[1].trim());
|
|
39
|
+
}
|
|
40
|
+
const objectMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
41
|
+
if (objectMatch) {
|
|
42
|
+
return JSON.parse(objectMatch[0]);
|
|
43
|
+
}
|
|
44
|
+
return JSON.parse(cleaned);
|
|
45
|
+
}
|
|
46
|
+
function extractNumber(text) {
|
|
47
|
+
const cleaned = stripReasoningTokens(text).trim();
|
|
48
|
+
const match = cleaned.match(/(\d+\.?\d*)/);
|
|
49
|
+
if (match) return parseFloat(match[1]);
|
|
50
|
+
return parseFloat(cleaned);
|
|
51
|
+
}
|
|
8
52
|
|
|
9
53
|
// src/prompts/index.ts
|
|
10
54
|
import fs from "fs";
|
|
@@ -92,59 +136,15 @@ ${truncated}
|
|
|
92
136
|
});
|
|
93
137
|
}
|
|
94
138
|
|
|
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
139
|
export {
|
|
140
|
+
stripReasoningTokens,
|
|
141
|
+
extractJson,
|
|
142
|
+
extractNumber,
|
|
140
143
|
loadPrompt,
|
|
141
144
|
buildExtractionPrompt,
|
|
142
145
|
buildSummaryPrompt,
|
|
143
146
|
buildTitlePrompt,
|
|
144
147
|
buildSimilarityPrompt,
|
|
145
|
-
buildClassificationPrompt
|
|
146
|
-
stripReasoningTokens,
|
|
147
|
-
extractJson,
|
|
148
|
-
extractNumber
|
|
148
|
+
buildClassificationPrompt
|
|
149
149
|
};
|
|
150
|
-
//# sourceMappingURL=chunk-
|
|
150
|
+
//# sourceMappingURL=chunk-KYL67SKZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/intelligence/response.ts","../src/prompts/index.ts"],"sourcesContent":["/**\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","/**\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"],"mappings":"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","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
|
STDIN_TIMEOUT_MS
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-B6WVNDA5.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-
|
|
21
|
+
//# sourceMappingURL=chunk-ND4VK6C7.js.map
|