obsidian-brain-mcp 0.1.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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +304 -0
  5. package/dist/tools/agent-dialogue.d.ts +5 -0
  6. package/dist/tools/agent-dialogue.js +121 -0
  7. package/dist/tools/consolidate-memory.d.ts +6 -0
  8. package/dist/tools/consolidate-memory.js +87 -0
  9. package/dist/tools/create-agent.d.ts +6 -0
  10. package/dist/tools/create-agent.js +47 -0
  11. package/dist/tools/detect-conflicts.d.ts +4 -0
  12. package/dist/tools/detect-conflicts.js +75 -0
  13. package/dist/tools/evolve-belief.d.ts +8 -0
  14. package/dist/tools/evolve-belief.js +57 -0
  15. package/dist/tools/get-agent-context.d.ts +2 -0
  16. package/dist/tools/get-agent-context.js +70 -0
  17. package/dist/tools/import-notes.d.ts +7 -0
  18. package/dist/tools/import-notes.js +64 -0
  19. package/dist/tools/link-knowledge.d.ts +8 -0
  20. package/dist/tools/link-knowledge.js +31 -0
  21. package/dist/tools/list-agents.d.ts +2 -0
  22. package/dist/tools/list-agents.js +22 -0
  23. package/dist/tools/promote-pattern.d.ts +9 -0
  24. package/dist/tools/promote-pattern.js +36 -0
  25. package/dist/tools/query-agent.d.ts +5 -0
  26. package/dist/tools/query-agent.js +109 -0
  27. package/dist/tools/record-decision.d.ts +9 -0
  28. package/dist/tools/record-decision.js +55 -0
  29. package/dist/tools/search-across-agents.d.ts +4 -0
  30. package/dist/tools/search-across-agents.js +77 -0
  31. package/dist/tools/suggest-agents.d.ts +4 -0
  32. package/dist/tools/suggest-agents.js +28 -0
  33. package/dist/types.d.ts +70 -0
  34. package/dist/types.js +1 -0
  35. package/dist/vault.d.ts +13 -0
  36. package/dist/vault.js +131 -0
  37. package/package.json +46 -0
@@ -0,0 +1,47 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { agentDir, agentExists, writeMarkdown, getAllAgentNames, readMarkdown, updateRegistry, } from "../vault.js";
4
+ export async function createAgent(input) {
5
+ const { name, description, scope } = input;
6
+ if (await agentExists(name)) {
7
+ throw new Error(`Agent "${name}" already exists.`);
8
+ }
9
+ const dir = agentDir(name);
10
+ await fs.mkdir(path.join(dir, "beliefs"), { recursive: true });
11
+ await fs.mkdir(path.join(dir, "decisions"), { recursive: true });
12
+ await fs.mkdir(path.join(dir, "patterns"), { recursive: true });
13
+ const created = new Date().toISOString().slice(0, 10);
14
+ const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
15
+ const body = `# ${capitalized}
16
+
17
+ ${description}
18
+
19
+ ## スコープ
20
+ ${scope}
21
+
22
+ ## 統計
23
+ - Beliefs: 0
24
+ - Decisions: 0
25
+ - Patterns: 0
26
+ `;
27
+ await writeMarkdown(path.join(dir, "_agent.md"), {
28
+ name,
29
+ description,
30
+ scope,
31
+ created,
32
+ }, body);
33
+ // Update registry with all agents
34
+ const allNames = await getAllAgentNames();
35
+ const agents = [];
36
+ for (const n of allNames) {
37
+ const { data } = await readMarkdown(path.join(agentDir(n), "_agent.md"));
38
+ agents.push({
39
+ name: data.name ?? n,
40
+ description: data.description ?? "",
41
+ scope: data.scope ?? "",
42
+ created: data.created ?? "",
43
+ });
44
+ }
45
+ await updateRegistry(agents);
46
+ return `Agent "${name}" created successfully.`;
47
+ }
@@ -0,0 +1,4 @@
1
+ export interface DetectConflictsInput {
2
+ agent?: string;
3
+ }
4
+ export declare function detectConflicts(input: DetectConflictsInput): Promise<string>;
@@ -0,0 +1,75 @@
1
+ import path from "node:path";
2
+ import { agentDir, agentExists, getAllAgentNames, readMarkdown, listFiles, } from "../vault.js";
3
+ function extractKeywords(text) {
4
+ const words = text
5
+ .toLowerCase()
6
+ .split(/[\s,.\-;:!?()[\]{}'"、。・「」()]+/)
7
+ .filter((w) => w.length > 1);
8
+ return new Set(words);
9
+ }
10
+ export async function detectConflicts(input) {
11
+ const agents = input.agent != null
12
+ ? [input.agent]
13
+ : await getAllAgentNames();
14
+ if (input.agent != null && !(await agentExists(input.agent))) {
15
+ throw new Error(`Agent "${input.agent}" not found.`);
16
+ }
17
+ // Collect all active beliefs with keywords
18
+ const allBeliefs = [];
19
+ for (const agent of agents) {
20
+ const dir = agentDir(agent);
21
+ const beliefFiles = await listFiles(path.join(dir, "beliefs"), ".md");
22
+ for (const file of beliefFiles) {
23
+ const { data, content } = await readMarkdown(path.join(dir, "beliefs", file));
24
+ if (data.superseded_by)
25
+ continue;
26
+ const belief = {
27
+ title: data.title ?? file.replace(/\.md$/, ""),
28
+ content: content.trim(),
29
+ agent: data.agent ?? agent,
30
+ confidence: data.confidence ?? 0,
31
+ formed: data.formed ?? "",
32
+ reasoning: data.reasoning ?? "",
33
+ supersedes: data.supersedes,
34
+ superseded_by: data.superseded_by,
35
+ evidence_count: data.evidence_count ?? 0,
36
+ filename: file,
37
+ };
38
+ const text = `${belief.title} ${belief.content}`;
39
+ const keywords = extractKeywords(text);
40
+ allBeliefs.push({ belief, keywords });
41
+ }
42
+ }
43
+ // Find pairs with 3+ shared keywords
44
+ const conflicts = [];
45
+ for (let i = 0; i < allBeliefs.length; i++) {
46
+ for (let j = i + 1; j < allBeliefs.length; j++) {
47
+ const a = allBeliefs[i];
48
+ const b = allBeliefs[j];
49
+ const shared = [];
50
+ for (const kw of a.keywords) {
51
+ if (b.keywords.has(kw)) {
52
+ shared.push(kw);
53
+ }
54
+ }
55
+ if (shared.length >= 3) {
56
+ conflicts.push({
57
+ a: a.belief,
58
+ b: b.belief,
59
+ sharedKeywords: shared,
60
+ confidenceDiff: Math.abs(a.belief.confidence - b.belief.confidence),
61
+ });
62
+ }
63
+ }
64
+ }
65
+ if (conflicts.length === 0) {
66
+ return "矛盾候補は検出されませんでした。";
67
+ }
68
+ // Sort by confidence difference descending
69
+ conflicts.sort((x, y) => y.confidenceDiff - x.confidenceDiff);
70
+ const sections = [`## 矛盾候補の検出結果`];
71
+ for (const c of conflicts) {
72
+ sections.push(`\n- **${c.a.title}** (${c.a.agent}, confidence: ${c.a.confidence}) vs **${c.b.title}** (${c.b.agent}, confidence: ${c.b.confidence})\n 共有キーワード: ${c.sharedKeywords.join(", ")}`);
73
+ }
74
+ return sections.join("\n");
75
+ }
@@ -0,0 +1,8 @@
1
+ export interface EvolveBeliefInput {
2
+ agent: string;
3
+ title: string;
4
+ content: string;
5
+ reasoning: string;
6
+ supersedes?: string;
7
+ }
8
+ export declare function evolveBelief(input: EvolveBeliefInput): Promise<string>;
@@ -0,0 +1,57 @@
1
+ import path from "node:path";
2
+ import slugify from "slugify";
3
+ import { agentDir, agentExists, writeMarkdown, readMarkdown, } from "../vault.js";
4
+ export async function evolveBelief(input) {
5
+ const { agent, title, content, reasoning, supersedes } = input;
6
+ if (!(await agentExists(agent))) {
7
+ throw new Error(`Agent "${agent}" not found.`);
8
+ }
9
+ const date = new Date().toISOString().slice(0, 10);
10
+ const slug = slugify(title, { lower: true, strict: true });
11
+ const newFilename = `${slug}.md`;
12
+ const beliefsDir = path.join(agentDir(agent), "beliefs");
13
+ const newFilePath = path.join(beliefsDir, newFilename);
14
+ // Handle supersedes
15
+ if (supersedes) {
16
+ const oldName = supersedes.endsWith(".md") ? supersedes : `${supersedes}.md`;
17
+ const oldPath = path.join(beliefsDir, oldName);
18
+ // Read old belief and mark as superseded
19
+ const { data: oldData, content: oldContent } = await readMarkdown(oldPath);
20
+ oldData.superseded_by = newFilename;
21
+ await writeMarkdown(oldPath, oldData, oldContent);
22
+ // Create new belief with supersedes reference
23
+ const body = `# ${title}
24
+
25
+ ${content}
26
+
27
+ ## 根拠
28
+ ${reasoning}
29
+ `;
30
+ await writeMarkdown(newFilePath, {
31
+ type: "belief",
32
+ agent,
33
+ confidence: 0.7,
34
+ formed: date,
35
+ supersedes: oldName,
36
+ evidence_count: 1,
37
+ }, body);
38
+ return `Belief evolved: ${oldName} → ${newFilename}`;
39
+ }
40
+ // New belief (no supersedes)
41
+ const body = `# ${title}
42
+
43
+ ${content}
44
+
45
+ ## 根拠
46
+ ${reasoning}
47
+ `;
48
+ await writeMarkdown(newFilePath, {
49
+ type: "belief",
50
+ agent,
51
+ confidence: 0.7,
52
+ formed: date,
53
+ supersedes: null,
54
+ evidence_count: 1,
55
+ }, body);
56
+ return `Belief created: ${newFilename}`;
57
+ }
@@ -0,0 +1,2 @@
1
+ import { AgentContext } from "../types.js";
2
+ export declare function getAgentContext(agentName: string): Promise<AgentContext>;
@@ -0,0 +1,70 @@
1
+ import path from "node:path";
2
+ import { agentDir, agentExists, readMarkdown, listFiles } from "../vault.js";
3
+ export async function getAgentContext(agentName) {
4
+ if (!(await agentExists(agentName))) {
5
+ throw new Error(`Agent "${agentName}" not found.`);
6
+ }
7
+ const dir = agentDir(agentName);
8
+ // Identity
9
+ const { data: agentData } = await readMarkdown(path.join(dir, "_agent.md"));
10
+ const identity = {
11
+ name: agentData.name ?? agentName,
12
+ description: agentData.description ?? "",
13
+ scope: agentData.scope ?? "",
14
+ created: agentData.created ?? "",
15
+ };
16
+ // Beliefs (active only — exclude superseded)
17
+ const beliefFiles = await listFiles(path.join(dir, "beliefs"), ".md");
18
+ const allBeliefs = [];
19
+ for (const file of beliefFiles) {
20
+ const { data, content } = await readMarkdown(path.join(dir, "beliefs", file));
21
+ allBeliefs.push({
22
+ title: data.title ?? "",
23
+ content: content.trim(),
24
+ agent: data.agent ?? agentName,
25
+ confidence: data.confidence ?? 0,
26
+ formed: data.formed ?? "",
27
+ reasoning: data.reasoning ?? "",
28
+ supersedes: data.supersedes,
29
+ superseded_by: data.superseded_by,
30
+ evidence_count: data.evidence_count ?? 0,
31
+ filename: file,
32
+ });
33
+ }
34
+ const beliefs = allBeliefs.filter((b) => !b.superseded_by);
35
+ // Decisions (sorted by date descending)
36
+ const decisionFiles = await listFiles(path.join(dir, "decisions"), ".md");
37
+ const decisions = [];
38
+ for (const file of decisionFiles) {
39
+ const { data, content } = await readMarkdown(path.join(dir, "decisions", file));
40
+ decisions.push({
41
+ title: data.title ?? "",
42
+ agent: data.agent ?? agentName,
43
+ context: data.context ?? "",
44
+ decision: data.decision ?? "",
45
+ reasoning: data.reasoning ?? "",
46
+ tags: data.tags ?? [],
47
+ date: data.date ?? "",
48
+ outcome: data.outcome ?? "",
49
+ filename: file,
50
+ });
51
+ }
52
+ decisions.sort((a, b) => (b.date > a.date ? 1 : b.date < a.date ? -1 : 0));
53
+ // Patterns
54
+ const patternFiles = await listFiles(path.join(dir, "patterns"), ".md");
55
+ const patterns = [];
56
+ for (const file of patternFiles) {
57
+ const { data, content } = await readMarkdown(path.join(dir, "patterns", file));
58
+ patterns.push({
59
+ title: data.title ?? "",
60
+ agent: data.agent ?? agentName,
61
+ description: data.description ?? "",
62
+ conditions: data.conditions ?? "",
63
+ confidence: data.confidence ?? 0,
64
+ derived_from: data.derived_from ?? [],
65
+ min_evidence: data.min_evidence ?? 0,
66
+ filename: file,
67
+ });
68
+ }
69
+ return { identity, beliefs, decisions, patterns };
70
+ }
@@ -0,0 +1,7 @@
1
+ export interface ImportNotesInput {
2
+ agent: string;
3
+ note_path: string;
4
+ import_as: "belief" | "decision" | "pattern";
5
+ title?: string;
6
+ }
7
+ export declare function importNotes(input: ImportNotesInput): Promise<string>;
@@ -0,0 +1,64 @@
1
+ import path from "node:path";
2
+ import slugify from "slugify";
3
+ import { agentDir, agentExists, readMarkdown, writeMarkdown, VAULT_BASE, } from "../vault.js";
4
+ export async function importNotes(input) {
5
+ const { agent, note_path, import_as } = input;
6
+ if (!(await agentExists(agent))) {
7
+ throw new Error(`Agent "${agent}" not found.`);
8
+ }
9
+ // Resolve note path
10
+ const resolvedPath = path.isAbsolute(note_path)
11
+ ? note_path
12
+ : path.join(VAULT_BASE, note_path);
13
+ // Read the source note
14
+ const { data: existingData, content } = await readMarkdown(resolvedPath);
15
+ // Derive title
16
+ const title = input.title ?? path.basename(resolvedPath, ".md");
17
+ const today = new Date().toISOString().slice(0, 10);
18
+ const slug = slugify(title, { lower: true, strict: true });
19
+ // Build frontmatter based on import_as, merge with existing (existing takes priority)
20
+ let baseFrontmatter;
21
+ switch (import_as) {
22
+ case "belief":
23
+ baseFrontmatter = {
24
+ type: "belief",
25
+ agent,
26
+ confidence: 0.7,
27
+ formed: today,
28
+ evidence_count: 1,
29
+ title,
30
+ };
31
+ break;
32
+ case "decision":
33
+ baseFrontmatter = {
34
+ type: "decision",
35
+ agent,
36
+ date: today,
37
+ tags: [],
38
+ outcome: "imported",
39
+ title,
40
+ };
41
+ break;
42
+ case "pattern":
43
+ baseFrontmatter = {
44
+ type: "pattern",
45
+ agent,
46
+ confidence: 0.5,
47
+ derived_from: [note_path],
48
+ min_evidence: 1,
49
+ title,
50
+ };
51
+ break;
52
+ }
53
+ // Merge: existing frontmatter fields take priority over base
54
+ const mergedFrontmatter = { ...baseFrontmatter, ...existingData };
55
+ // Ensure type and agent are always set correctly
56
+ mergedFrontmatter.type = import_as;
57
+ mergedFrontmatter.agent = agent;
58
+ // Save to agent directory
59
+ const destDir = path.join(agentDir(agent), `${import_as}s`);
60
+ const filename = `${slug}.md`;
61
+ const destPath = path.join(destDir, filename);
62
+ await writeMarkdown(destPath, mergedFrontmatter, content);
63
+ return `インポート完了: ${path.basename(resolvedPath)} → ${agent}/${import_as}s/${filename}`;
64
+ }
@@ -0,0 +1,8 @@
1
+ export interface LinkKnowledgeInput {
2
+ source_agent: string;
3
+ source_file: string;
4
+ target_agent: string;
5
+ target_file: string;
6
+ relationship: string;
7
+ }
8
+ export declare function linkKnowledge(input: LinkKnowledgeInput): Promise<string>;
@@ -0,0 +1,31 @@
1
+ import path from "node:path";
2
+ import slugify from "slugify";
3
+ import { agentDir, agentExists, writeMarkdown } from "../vault.js";
4
+ export async function linkKnowledge(input) {
5
+ const { source_agent, source_file, target_agent, target_file, relationship } = input;
6
+ if (!(await agentExists(source_agent))) {
7
+ throw new Error(`Agent "${source_agent}" not found.`);
8
+ }
9
+ if (!(await agentExists(target_agent))) {
10
+ throw new Error(`Agent "${target_agent}" not found.`);
11
+ }
12
+ const linkDir = path.join(agentDir(source_agent), "knowledge-links");
13
+ const slug = slugify(`${source_file}-${relationship}-${target_file}`, {
14
+ lower: true,
15
+ strict: true,
16
+ });
17
+ const filename = `${slug}.md`;
18
+ const filePath = path.join(linkDir, filename);
19
+ const body = `[[${source_file}]] --${relationship}--> [[${target_file}]] (${target_agent})
20
+ `;
21
+ await writeMarkdown(filePath, {
22
+ type: "link",
23
+ source_agent,
24
+ source_file,
25
+ target_agent,
26
+ target_file,
27
+ relationship,
28
+ created: new Date().toISOString().slice(0, 10),
29
+ }, body);
30
+ return `Knowledge link created: ${filename} (${source_agent}/${source_file} --${relationship}--> ${target_agent}/${target_file})`;
31
+ }
@@ -0,0 +1,2 @@
1
+ import { AgentSummary } from "../types.js";
2
+ export declare function listAgents(): Promise<AgentSummary[]>;
@@ -0,0 +1,22 @@
1
+ import path from "node:path";
2
+ import { agentDir, getAllAgentNames, readMarkdown, listFiles, } from "../vault.js";
3
+ export async function listAgents() {
4
+ const names = await getAllAgentNames();
5
+ const summaries = [];
6
+ for (const name of names) {
7
+ const dir = agentDir(name);
8
+ const { data } = await readMarkdown(path.join(dir, "_agent.md"));
9
+ const beliefs = await listFiles(path.join(dir, "beliefs"), ".md");
10
+ const decisions = await listFiles(path.join(dir, "decisions"), ".md");
11
+ const patterns = await listFiles(path.join(dir, "patterns"), ".md");
12
+ summaries.push({
13
+ name: data.name ?? name,
14
+ description: data.description ?? "",
15
+ scope: data.scope ?? "",
16
+ beliefCount: beliefs.length,
17
+ decisionCount: decisions.length,
18
+ patternCount: patterns.length,
19
+ });
20
+ }
21
+ return summaries;
22
+ }
@@ -0,0 +1,9 @@
1
+ export interface PromotePatternInput {
2
+ agent: string;
3
+ title: string;
4
+ description: string;
5
+ conditions: string;
6
+ decision_refs: string[];
7
+ confidence: number;
8
+ }
9
+ export declare function promotePattern(input: PromotePatternInput): Promise<string>;
@@ -0,0 +1,36 @@
1
+ import path from "node:path";
2
+ import slugify from "slugify";
3
+ import { agentDir, agentExists, writeMarkdown } from "../vault.js";
4
+ export async function promotePattern(input) {
5
+ const { agent, title, description, conditions, decision_refs, confidence } = input;
6
+ if (!(await agentExists(agent))) {
7
+ throw new Error(`Agent "${agent}" not found.`);
8
+ }
9
+ const slug = slugify(title, { lower: true, strict: true });
10
+ const filename = `${slug}.md`;
11
+ const filePath = path.join(agentDir(agent), "patterns", filename);
12
+ const wikilinks = decision_refs
13
+ .map((ref) => {
14
+ const name = ref.endsWith(".md") ? ref.slice(0, -3) : ref;
15
+ return `- [[${name}]]`;
16
+ })
17
+ .join("\n");
18
+ const body = `# ${title}
19
+
20
+ ${description}
21
+
22
+ ## 適用条件
23
+ ${conditions}
24
+
25
+ ## 根拠となる判断
26
+ ${wikilinks}
27
+ `;
28
+ await writeMarkdown(filePath, {
29
+ type: "pattern",
30
+ agent,
31
+ confidence,
32
+ derived_from: decision_refs,
33
+ min_evidence: decision_refs.length,
34
+ }, body);
35
+ return `Pattern promoted: ${filename} (confidence: ${confidence}, based on ${decision_refs.length} decisions)`;
36
+ }
@@ -0,0 +1,5 @@
1
+ export interface QueryAgentInput {
2
+ agent: string;
3
+ question: string;
4
+ }
5
+ export declare function queryAgent(input: QueryAgentInput): Promise<string>;
@@ -0,0 +1,109 @@
1
+ import path from "node:path";
2
+ import { agentDir, agentExists, readMarkdown, listFiles, } from "../vault.js";
3
+ export async function queryAgent(input) {
4
+ const { agent, question } = input;
5
+ if (!(await agentExists(agent))) {
6
+ throw new Error(`Agent "${agent}" not found.`);
7
+ }
8
+ const dir = agentDir(agent);
9
+ const keywords = question
10
+ .toLowerCase()
11
+ .split(/\s+/)
12
+ .filter((w) => w.length > 1);
13
+ function matches(text) {
14
+ const lower = text.toLowerCase();
15
+ return keywords.some((kw) => lower.includes(kw));
16
+ }
17
+ // Read patterns
18
+ const patternFiles = await listFiles(path.join(dir, "patterns"), ".md");
19
+ const matchedPatterns = [];
20
+ for (const file of patternFiles) {
21
+ const { data, content } = await readMarkdown(path.join(dir, "patterns", file));
22
+ const tags = (data.tags ?? []).join(" ");
23
+ const searchable = `${data.title ?? ""} ${data.description ?? ""} ${tags} ${content}`;
24
+ if (matches(searchable)) {
25
+ matchedPatterns.push({
26
+ title: data.title ?? file.replace(/\.md$/, ""),
27
+ agent: data.agent ?? agent,
28
+ description: data.description ?? content.trim(),
29
+ conditions: data.conditions ?? "",
30
+ confidence: data.confidence ?? 0,
31
+ derived_from: data.derived_from ?? [],
32
+ min_evidence: data.min_evidence ?? 0,
33
+ filename: file,
34
+ });
35
+ }
36
+ }
37
+ // Read beliefs (active only)
38
+ const beliefFiles = await listFiles(path.join(dir, "beliefs"), ".md");
39
+ const matchedBeliefs = [];
40
+ for (const file of beliefFiles) {
41
+ const { data, content } = await readMarkdown(path.join(dir, "beliefs", file));
42
+ if (data.superseded_by)
43
+ continue;
44
+ const tags = (data.tags ?? []).join(" ");
45
+ const searchable = `${data.title ?? ""} ${tags} ${content}`;
46
+ if (matches(searchable)) {
47
+ matchedBeliefs.push({
48
+ title: data.title ?? file.replace(/\.md$/, ""),
49
+ content: content.trim(),
50
+ agent: data.agent ?? agent,
51
+ confidence: data.confidence ?? 0,
52
+ formed: data.formed ?? "",
53
+ reasoning: data.reasoning ?? "",
54
+ supersedes: data.supersedes,
55
+ superseded_by: data.superseded_by,
56
+ evidence_count: data.evidence_count ?? 0,
57
+ filename: file,
58
+ });
59
+ }
60
+ }
61
+ // Read decisions
62
+ const decisionFiles = await listFiles(path.join(dir, "decisions"), ".md");
63
+ const matchedDecisions = [];
64
+ for (const file of decisionFiles) {
65
+ const { data, content } = await readMarkdown(path.join(dir, "decisions", file));
66
+ const tags = (data.tags ?? []).join(" ");
67
+ const searchable = `${data.title ?? ""} ${tags} ${content}`;
68
+ if (matches(searchable)) {
69
+ matchedDecisions.push({
70
+ title: data.title ?? file.replace(/\.md$/, ""),
71
+ agent: data.agent ?? agent,
72
+ context: data.context ?? "",
73
+ decision: data.decision ?? "",
74
+ reasoning: data.reasoning ?? "",
75
+ tags: data.tags ?? [],
76
+ date: data.date ?? "",
77
+ outcome: data.outcome ?? "",
78
+ filename: file,
79
+ });
80
+ }
81
+ }
82
+ matchedDecisions.sort((a, b) => b.date > a.date ? 1 : b.date < a.date ? -1 : 0);
83
+ // Build response
84
+ const sections = [`## ${agent} の知識`];
85
+ if (matchedPatterns.length > 0) {
86
+ sections.push("\n### パターン");
87
+ for (const p of matchedPatterns) {
88
+ sections.push(`- ${p.title}: ${p.description} (confidence: ${p.confidence})`);
89
+ }
90
+ }
91
+ if (matchedBeliefs.length > 0) {
92
+ sections.push("\n### 信念");
93
+ for (const b of matchedBeliefs) {
94
+ sections.push(`- ${b.title}: ${b.content} (confidence: ${b.confidence})`);
95
+ }
96
+ }
97
+ if (matchedDecisions.length > 0) {
98
+ sections.push("\n### 関連する判断");
99
+ for (const d of matchedDecisions) {
100
+ sections.push(`- [${d.date}] ${d.title}: ${d.decision}`);
101
+ }
102
+ }
103
+ if (matchedPatterns.length === 0 &&
104
+ matchedBeliefs.length === 0 &&
105
+ matchedDecisions.length === 0) {
106
+ sections.push(`\n「${question}」に関連する知識は見つかりませんでした。`);
107
+ }
108
+ return sections.join("\n");
109
+ }
@@ -0,0 +1,9 @@
1
+ export interface RecordDecisionInput {
2
+ agent: string;
3
+ title: string;
4
+ context: string;
5
+ decision: string;
6
+ reasoning: string;
7
+ tags: string[];
8
+ }
9
+ export declare function recordDecision(input: RecordDecisionInput): Promise<string>;
@@ -0,0 +1,55 @@
1
+ import path from "node:path";
2
+ import slugify from "slugify";
3
+ import { agentDir, agentExists, writeMarkdown, readMarkdown, listFiles, } from "../vault.js";
4
+ export async function recordDecision(input) {
5
+ const { agent, title, context, decision, reasoning, tags } = input;
6
+ if (!(await agentExists(agent))) {
7
+ throw new Error(`Agent "${agent}" not found.`);
8
+ }
9
+ const date = new Date().toISOString().slice(0, 10);
10
+ const slug = slugify(title, { lower: true, strict: true });
11
+ const filename = `${date}-${slug}.md`;
12
+ const filePath = path.join(agentDir(agent), "decisions", filename);
13
+ const body = `# ${title}
14
+
15
+ ## 状況
16
+ ${context}
17
+
18
+ ## 判断
19
+ ${decision}
20
+
21
+ ## 理由
22
+ ${reasoning}
23
+
24
+ ## 結果
25
+ pending
26
+ `;
27
+ await writeMarkdown(filePath, {
28
+ type: "decision",
29
+ agent,
30
+ tags,
31
+ date,
32
+ outcome: "pending",
33
+ }, body);
34
+ // Check for pattern promotion candidates
35
+ let message = `Decision recorded: ${filename}`;
36
+ const decisionDir = path.join(agentDir(agent), "decisions");
37
+ const files = await listFiles(decisionDir, ".md");
38
+ const tagCounts = new Map();
39
+ for (const file of files) {
40
+ const { data } = await readMarkdown(path.join(decisionDir, file));
41
+ const fileTags = data.tags ?? [];
42
+ for (const tag of tags) {
43
+ if (fileTags.includes(tag)) {
44
+ tagCounts.set(tag, (tagCounts.get(tag) ?? 0) + 1);
45
+ }
46
+ }
47
+ }
48
+ const promotionCandidates = [...tagCounts.entries()].filter(([, count]) => count >= 3);
49
+ if (promotionCandidates.length > 0) {
50
+ for (const [tag, count] of promotionCandidates) {
51
+ message += `\nパターン昇格の候補があります: タグ[${tag}]で${count}件の判断があります`;
52
+ }
53
+ }
54
+ return message;
55
+ }
@@ -0,0 +1,4 @@
1
+ export interface SearchAcrossAgentsInput {
2
+ query: string;
3
+ }
4
+ export declare function searchAcrossAgents(input: SearchAcrossAgentsInput): Promise<string>;