@monoes/monograph 1.2.1 → 1.2.3

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 (33) hide show
  1. package/.monomind/loops/mastermind-review-1782162423447.json +16 -0
  2. package/dist/src/claude-cli.d.ts +3 -0
  3. package/dist/src/claude-cli.d.ts.map +1 -0
  4. package/dist/src/claude-cli.js +61 -0
  5. package/dist/src/claude-cli.js.map +1 -0
  6. package/dist/src/cli/skill-gen.js +3 -0
  7. package/dist/src/cli/skill-gen.js.map +1 -1
  8. package/dist/src/graph/node-search.d.ts.map +1 -1
  9. package/dist/src/graph/node-search.js.map +1 -1
  10. package/dist/src/graph/pagerank.d.ts.map +1 -1
  11. package/dist/src/graph/pagerank.js.map +1 -1
  12. package/dist/src/graph/subgraph.d.ts.map +1 -1
  13. package/dist/src/graph/subgraph.js.map +1 -1
  14. package/dist/src/mcp-tools/wiki-build.d.ts.map +1 -1
  15. package/dist/src/mcp-tools/wiki-build.js +0 -4
  16. package/dist/src/mcp-tools/wiki-build.js.map +1 -1
  17. package/dist/src/pipeline/orchestrator.d.ts +2 -0
  18. package/dist/src/pipeline/phases/llm-extract.d.ts.map +1 -1
  19. package/dist/src/pipeline/phases/llm-extract.js +10 -22
  20. package/dist/src/pipeline/phases/llm-extract.js.map +1 -1
  21. package/dist/src/wiki/wiki-generator.d.ts.map +1 -1
  22. package/dist/src/wiki/wiki-generator.js +9 -13
  23. package/dist/src/wiki/wiki-generator.js.map +1 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/package.json +1 -1
  26. package/src/claude-cli.ts +59 -0
  27. package/src/cli/skill-gen.ts +3 -0
  28. package/src/graph/node-search.ts +3 -2
  29. package/src/graph/pagerank.ts +3 -2
  30. package/src/graph/subgraph.ts +2 -1
  31. package/src/mcp-tools/wiki-build.ts +0 -5
  32. package/src/pipeline/phases/llm-extract.ts +10 -21
  33. package/src/wiki/wiki-generator.ts +9 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monograph",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "type": "module",
5
5
  "description": "Native TypeScript code intelligence engine for monomind",
6
6
  "main": "dist/src/index.js",
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Thin wrapper around `claude --print` for LLM calls inside monograph.
3
+ * Reuses Claude Code's existing auth — no ANTHROPIC_API_KEY needed.
4
+ */
5
+ import { spawn, execSync } from 'child_process';
6
+
7
+ const DEFAULT_TIMEOUT_MS = 60_000;
8
+ let _available: boolean | null = null;
9
+
10
+ export function isClaudeCliAvailable(): boolean {
11
+ if (_available !== null) return _available;
12
+ try {
13
+ execSync('claude --version', { encoding: 'utf-8', stdio: 'pipe', timeout: 5000, windowsHide: true });
14
+ _available = true;
15
+ } catch {
16
+ _available = false;
17
+ }
18
+ return _available;
19
+ }
20
+
21
+ export function claudeCliCall(prompt: string, timeoutMs = DEFAULT_TIMEOUT_MS): Promise<string> {
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn(
24
+ 'claude',
25
+ ['--print', '--model', 'haiku', '--strict-mcp-config', '--no-session-persistence', '--', prompt],
26
+ { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true },
27
+ );
28
+
29
+ let stdout = '';
30
+ let stderr = '';
31
+ let settled = false;
32
+
33
+ const timer = setTimeout(() => {
34
+ if (settled) return;
35
+ settled = true;
36
+ try { child.kill('SIGTERM'); } catch { /* ignore */ }
37
+ setTimeout(() => { try { child.kill('SIGKILL'); } catch { /* ignore */ } }, 3000);
38
+ reject(new Error(`claude --print timed out after ${timeoutMs}ms`));
39
+ }, timeoutMs);
40
+
41
+ child.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });
42
+ child.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });
43
+
44
+ child.on('close', (code) => {
45
+ if (settled) return;
46
+ settled = true;
47
+ clearTimeout(timer);
48
+ if (code === 0) resolve(stdout.trim());
49
+ else reject(new Error(stderr.trim() || `claude exited with code ${code}`));
50
+ });
51
+
52
+ child.on('error', (err) => {
53
+ if (settled) return;
54
+ settled = true;
55
+ clearTimeout(timer);
56
+ reject(err);
57
+ });
58
+ });
59
+ }
@@ -245,6 +245,9 @@ function renderSkillMarkdown(
245
245
  lines.push('---');
246
246
  lines.push(`name: ${toKebabName(label)}`);
247
247
  lines.push(`description: "Skill for the ${label} community. ${members.length} symbols."`);
248
+ lines.push(`type: Code Community`);
249
+ lines.push(`tags: [monograph, code-community]`);
250
+ lines.push(`timestamp: ${new Date().toISOString()}`);
248
251
  lines.push('---');
249
252
  lines.push('');
250
253
 
@@ -1,3 +1,4 @@
1
+ import Database from 'better-sqlite3';
1
2
  import type { MonographNode, NodeLabel } from '../types.js';
2
3
  import type { MonographDb } from '../storage/db.js';
3
4
 
@@ -55,13 +56,13 @@ function rowToNode(r: {
55
56
  // so the same query shape reuses the compiled statement across calls.
56
57
 
57
58
  // WeakMap ensures the cache is garbage-collected when the DB object is released.
58
- const stmtCache = new WeakMap<object, Map<string, ReturnType<MonographDb['prepare']>>>();
59
+ const stmtCache = new WeakMap<object, Map<string, Database.Statement>>();
59
60
 
60
61
  function getCachedStmt(
61
62
  db: MonographDb,
62
63
  key: string,
63
64
  buildSql: () => string,
64
- ): ReturnType<MonographDb['prepare']> {
65
+ ): Database.Statement {
65
66
  let dbCache = stmtCache.get(db);
66
67
  if (!dbCache) {
67
68
  dbCache = new Map();
@@ -1,3 +1,4 @@
1
+ import Database from 'better-sqlite3';
1
2
  import type { MonographDb } from '../storage/db.js';
2
3
 
3
4
  export interface PageRankOptions {
@@ -13,8 +14,8 @@ export interface PageRankOptions {
13
14
  // Per-DB statement cache — avoids recompiling SQL on every pageRank() call.
14
15
  // ---------------------------------------------------------------------------
15
16
  interface StmtCache {
16
- selectNodes: ReturnType<MonographDb['prepare']>;
17
- selectEdges: ReturnType<MonographDb['prepare']>;
17
+ selectNodes: Database.Statement;
18
+ selectEdges: Database.Statement;
18
19
  }
19
20
 
20
21
  const _stmtCache = new Map<string, StmtCache>();
@@ -1,3 +1,4 @@
1
+ import Database from 'better-sqlite3';
1
2
  import type { MonographNode, MonographEdge } from '../types.js';
2
3
  import type { MonographDb } from '../storage/db.js';
3
4
 
@@ -66,7 +67,7 @@ export function extractInducedSubgraph(db: MonographDb, nodeIds: string[]): Indu
66
67
  const stmts = getStmtCaches(db);
67
68
 
68
69
  // Chunk helper — caches prepared statements by chunk size to avoid re-prepare per call.
69
- function queryChunked<T>(ids: string[], chunkSize: number, stmtCache: Map<number, ReturnType<MonographDb['prepare']>>, sql: (ph: string) => string): T[] {
70
+ function queryChunked<T>(ids: string[], chunkSize: number, stmtCache: Map<number, Database.Statement>, sql: (ph: string) => string): T[] {
70
71
  const results: T[] = [];
71
72
  for (let i = 0; i < ids.length; i += chunkSize) {
72
73
  const chunk = ids.slice(i, i + chunkSize);
@@ -25,11 +25,6 @@ export async function runWikiBuildTool(
25
25
  db: MonographDb,
26
26
  input: WikiBuildToolInput,
27
27
  ): Promise<WikiBuildToolResult> {
28
- const apiKey = process.env['ANTHROPIC_API_KEY'];
29
- if (!apiKey && !input.llmClient) {
30
- return { error: 'ANTHROPIC_API_KEY not set' };
31
- }
32
-
33
28
  const opts: GenerateAllWikiPagesOptions = {
34
29
  force: input.force ?? false,
35
30
  model: input.model,
@@ -37,25 +37,11 @@ interface Triple {
37
37
  edge: string;
38
38
  }
39
39
 
40
- async function callClaude(content: string, apiKey: string): Promise<Triple[] | null> {
40
+ async function callClaude(content: string): Promise<Triple[] | null> {
41
41
  try {
42
- const res = await fetch('https://api.anthropic.com/v1/messages', {
43
- method: 'POST',
44
- headers: {
45
- 'x-api-key': apiKey,
46
- 'anthropic-version': '2023-06-01',
47
- 'content-type': 'application/json',
48
- },
49
- body: JSON.stringify({
50
- model: 'claude-haiku-4-5-20251001',
51
- max_tokens: 512,
52
- system: SYSTEM_PROMPT,
53
- messages: [{ role: 'user', content: content.slice(0, 1500) }],
54
- }),
55
- });
56
- if (!res.ok) return null;
57
- const data = await res.json() as { content: Array<{ text: string }> };
58
- const text = data.content[0]?.text ?? '[]';
42
+ const { claudeCliCall } = await import('../../claude-cli.js');
43
+ const prompt = `${SYSTEM_PROMPT}\n\n${content.slice(0, 1500)}`;
44
+ const text = await claudeCliCall(prompt, 30_000);
59
45
  const parsed = JSON.parse(text.match(/\[[\s\S]*\]/)?.[0] ?? '[]');
60
46
  return Array.isArray(parsed) ? parsed as Triple[] : null;
61
47
  } catch {
@@ -68,10 +54,14 @@ export const llmExtractPhase: PipelinePhase<LlmExtractOutput> = {
68
54
  deps: ['docs-parse', 'pdf-parse'],
69
55
 
70
56
  async execute(ctx: PipelineContext) {
71
- const apiKey = process.env.ANTHROPIC_API_KEY;
72
57
  const maxSections = ctx.options.llmMaxSections;
73
58
 
74
- if (!apiKey || maxSections <= 0 || ctx.options.codeOnly) {
59
+ if (maxSections <= 0 || ctx.options.codeOnly) {
60
+ return { triplesExtracted: 0, sectionsProcessed: 0 };
61
+ }
62
+
63
+ const { isClaudeCliAvailable } = await import('../../claude-cli.js');
64
+ if (!isClaudeCliAvailable()) {
75
65
  return { triplesExtracted: 0, sectionsProcessed: 0 };
76
66
  }
77
67
 
@@ -107,7 +97,6 @@ export const llmExtractPhase: PipelinePhase<LlmExtractOutput> = {
107
97
 
108
98
  const triples = await callClaude(
109
99
  `Section: "${section.name}"\n\n${content}`,
110
- apiKey,
111
100
  );
112
101
  if (!triples || triples.length === 0) continue;
113
102
 
@@ -145,22 +145,18 @@ export async function generateWikiPage(
145
145
  const result = await callLLM(prompt, options.llmConfig);
146
146
  content = result.text;
147
147
  } else {
148
- const apiKey = options?.apiKey ?? process.env['ANTHROPIC_API_KEY'];
149
- if (!apiKey) {
150
- throw new Error('ANTHROPIC_API_KEY not set');
148
+ const { claudeCliCall, isClaudeCliAvailable } = await import('../claude-cli.js');
149
+ if (!isClaudeCliAvailable()) {
150
+ throw new Error('Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code');
151
151
  }
152
- const { default: Anthropic } = await import('@anthropic-ai/sdk');
153
- const client = new Anthropic({ apiKey });
154
- const msg = await client.messages.create({
155
- model: options?.model ?? 'claude-haiku-4-5-20251001',
156
- max_tokens: 1024,
157
- messages: [{ role: 'user', content: prompt }],
158
- });
159
- const block = msg.content[0];
160
- content = (block?.type === 'text') ? block.text : '';
152
+ content = await claudeCliCall(prompt);
161
153
  }
162
154
 
163
- // 7. Persist to DB
155
+ // 7. Persist to DB — prepend OKF frontmatter if not already present
156
+ if (!content.startsWith('---')) {
157
+ const safeLabel = label.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
158
+ content = `---\ntype: Code Community Wiki\ntitle: "${safeLabel}"\ntags: [monograph, wiki]\ntimestamp: ${new Date().toISOString()}\n---\n\n${content}`;
159
+ }
164
160
  upsertWikiPage(db, communityIdStr, content);
165
161
 
166
162
  return content;