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.
- package/LICENSE +21 -0
- package/README.md +112 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +304 -0
- package/dist/tools/agent-dialogue.d.ts +5 -0
- package/dist/tools/agent-dialogue.js +121 -0
- package/dist/tools/consolidate-memory.d.ts +6 -0
- package/dist/tools/consolidate-memory.js +87 -0
- package/dist/tools/create-agent.d.ts +6 -0
- package/dist/tools/create-agent.js +47 -0
- package/dist/tools/detect-conflicts.d.ts +4 -0
- package/dist/tools/detect-conflicts.js +75 -0
- package/dist/tools/evolve-belief.d.ts +8 -0
- package/dist/tools/evolve-belief.js +57 -0
- package/dist/tools/get-agent-context.d.ts +2 -0
- package/dist/tools/get-agent-context.js +70 -0
- package/dist/tools/import-notes.d.ts +7 -0
- package/dist/tools/import-notes.js +64 -0
- package/dist/tools/link-knowledge.d.ts +8 -0
- package/dist/tools/link-knowledge.js +31 -0
- package/dist/tools/list-agents.d.ts +2 -0
- package/dist/tools/list-agents.js +22 -0
- package/dist/tools/promote-pattern.d.ts +9 -0
- package/dist/tools/promote-pattern.js +36 -0
- package/dist/tools/query-agent.d.ts +5 -0
- package/dist/tools/query-agent.js +109 -0
- package/dist/tools/record-decision.d.ts +9 -0
- package/dist/tools/record-decision.js +55 -0
- package/dist/tools/search-across-agents.d.ts +4 -0
- package/dist/tools/search-across-agents.js +77 -0
- package/dist/tools/suggest-agents.d.ts +4 -0
- package/dist/tools/suggest-agents.js +28 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.js +1 -0
- package/dist/vault.d.ts +13 -0
- package/dist/vault.js +131 -0
- 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,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,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,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,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,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,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,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,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,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
|
+
}
|