add familiar commands: capture, process, graph, health, briefing
This commit is contained in:
parent
a8a88a4553
commit
efba0f026c
|
|
@ -5,5 +5,6 @@ dist/
|
|||
*.log
|
||||
LOGS/
|
||||
TEMP/
|
||||
.familiar-test
|
||||
.DS_Store
|
||||
*.tsbuildinfo
|
||||
|
|
|
|||
108
src/index.ts
108
src/index.ts
|
|
@ -1068,6 +1068,114 @@ program
|
|||
console.log(` Every run compounds. The harness, not the model.\n`);
|
||||
});
|
||||
|
||||
// ── Familiar ────────────────────────────────────────────────
|
||||
|
||||
const familiar = program
|
||||
.command("familiar")
|
||||
.description("Familiar knowledge base — inbox-to-wiki processing");
|
||||
|
||||
familiar
|
||||
.command("capture <note>")
|
||||
.description("Quick capture — drop a note into inbox")
|
||||
.option("-s, --source <src>", "Source label (voice, web, idea, etc.)")
|
||||
.action(async (note: string, opts: { source?: string }) => {
|
||||
const { FamiliarKnowledge } = await import("./upgrades/familiar-knowledge.js");
|
||||
const f = new FamiliarKnowledge();
|
||||
const path = f.quickCapture(note, opts.source);
|
||||
console.log(`\n ✓ Captured to: ${path}\n`);
|
||||
});
|
||||
|
||||
familiar
|
||||
.command("process")
|
||||
.description("Process all inbox notes into wiki pages")
|
||||
.action(async () => {
|
||||
const { FamiliarKnowledge } = await import("./upgrades/familiar-knowledge.js");
|
||||
const f = new FamiliarKnowledge();
|
||||
const notes = f.scanInbox();
|
||||
|
||||
if (notes.length === 0) {
|
||||
console.log(` No notes in inbox/ to process.\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Default classifier — generates basic wiki pages from inbox content
|
||||
const classifier = (content: string) => {
|
||||
const firstLine = content.split("\n")[0]?.replace(/^#\s*/, "").trim() ?? "Untitled";
|
||||
const tags = ["inbox"];
|
||||
if (content.toLowerCase().includes("loop")) tags.push("loops");
|
||||
if (content.toLowerCase().includes("agent")) tags.push("agent");
|
||||
if (content.toLowerCase().includes("safety")) tags.push("safety");
|
||||
|
||||
return {
|
||||
title: firstLine,
|
||||
summary: content.split("\n").slice(1, 3).join(" ").trim().slice(0, 300),
|
||||
confidence: 0.5,
|
||||
tags,
|
||||
mentions: [],
|
||||
contradictions: [],
|
||||
};
|
||||
};
|
||||
|
||||
const processed = f.processAllInbox(classifier);
|
||||
f.autoCommit(`familiar: processed ${processed.length} inbox notes`);
|
||||
console.log(`\n Processed ${processed.length} notes:\n`);
|
||||
for (const p of processed) {
|
||||
console.log(` ✓ ${p.frontmatter.title}`);
|
||||
console.log(` Tags: ${p.frontmatter.tags.join(", ")}`);
|
||||
console.log(` Wiki: ${p.wikiPath}`);
|
||||
}
|
||||
console.log(``);
|
||||
});
|
||||
|
||||
familiar
|
||||
.command("graph")
|
||||
.description("Show knowledge graph")
|
||||
.action(async () => {
|
||||
const { FamiliarKnowledge } = await import("./upgrades/familiar-knowledge.js");
|
||||
const f = new FamiliarKnowledge();
|
||||
const graph = f.buildGraphIndex();
|
||||
console.log(`\n Knowledge Graph:`);
|
||||
console.log(` ${graph.nodes.length} pages, ${graph.edges.length} connections\n`);
|
||||
for (const node of graph.nodes) {
|
||||
const edges = graph.edges.filter((e) => e.source === node.id);
|
||||
console.log(` ${node.title}`);
|
||||
for (const e of edges) console.log(` → ${e.target}`);
|
||||
}
|
||||
console.log(``);
|
||||
});
|
||||
|
||||
familiar
|
||||
.command("health")
|
||||
.description("Audit wiki health — detect orphaned pages, low confidence, missing backlinks")
|
||||
.action(async () => {
|
||||
const { FamiliarKnowledge } = await import("./upgrades/familiar-knowledge.js");
|
||||
const f = new FamiliarKnowledge();
|
||||
const health = f.graphHealth();
|
||||
console.log(`\n Graph Health:\n`);
|
||||
console.log(` Pages: ${health.stats.totalPages}`);
|
||||
console.log(` Edges: ${health.stats.totalConnections}`);
|
||||
console.log(` Orphaned: ${health.stats.orphanedPages}`);
|
||||
console.log(` No backlinks: ${health.stats.pagesWithoutBacklinks}`);
|
||||
console.log(` Low confidence: ${health.stats.pagesWithLowConfidence}\n`);
|
||||
if (health.issues.length > 0) {
|
||||
console.log(` Issues:`);
|
||||
for (const issue of health.issues.slice(0, 5)) {
|
||||
console.log(` • ${issue}`);
|
||||
}
|
||||
}
|
||||
console.log(``);
|
||||
});
|
||||
|
||||
familiar
|
||||
.command("briefing")
|
||||
.description("Generate daily briefing page")
|
||||
.action(async () => {
|
||||
const { FamiliarKnowledge } = await import("./upgrades/familiar-knowledge.js");
|
||||
const f = new FamiliarKnowledge();
|
||||
const briefing = f.generateBriefing();
|
||||
console.log(`\n${briefing}\n`);
|
||||
});
|
||||
|
||||
// ── PAI Pi ───────────────────────────────────────────────────
|
||||
|
||||
const paiPi = program
|
||||
|
|
|
|||
|
|
@ -283,6 +283,77 @@ export class FamiliarKnowledge {
|
|||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/** Quick capture — drop a note into inbox/ with timestamp */
|
||||
quickCapture(content: string, source?: string): string {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
||||
const prefix = source ? `captured-from-${source}` : "quick-capture";
|
||||
const fileName = `${prefix}-${timestamp}.md`;
|
||||
const filePath = path.join(this.baseDir, "inbox", fileName);
|
||||
const header = source
|
||||
? `# Quick Capture (from ${source})\n\nCaptured: ${new Date().toISOString()}\n\n`
|
||||
: `# Quick Capture\n\nCaptured: ${new Date().toISOString()}\n\n`;
|
||||
fs.writeFileSync(filePath, header + content, "utf-8");
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/** Graph health audit — detect issues in the wiki */
|
||||
graphHealth(): GraphHealthReport {
|
||||
const graph = this.buildGraphIndex();
|
||||
const issues: string[] = [];
|
||||
const stats = {
|
||||
totalPages: graph.nodes.length,
|
||||
totalConnections: graph.edges.length,
|
||||
orphanedPages: 0,
|
||||
pagesWithLowConfidence: 0,
|
||||
pagesWithoutBacklinks: 0,
|
||||
stalePages: 0,
|
||||
};
|
||||
|
||||
for (const node of graph.nodes) {
|
||||
// Orphaned: no edges at all
|
||||
const hasEdges = graph.edges.some((e) => e.source === node.id || e.target === node.id);
|
||||
if (!hasEdges) {
|
||||
stats.orphanedPages++;
|
||||
issues.push(`Orphaned: "${node.title}" has no connections to other pages`);
|
||||
}
|
||||
|
||||
// Low confidence
|
||||
if (node.confidence < 0.3) {
|
||||
stats.pagesWithLowConfidence++;
|
||||
issues.push(`Low confidence: "${node.title}" (${node.confidence})`);
|
||||
}
|
||||
|
||||
// No backlinks (only check source edges since our graph has source→target direction)
|
||||
const hasIncoming = graph.edges.some((e) => e.target === node.id);
|
||||
if (!hasIncoming && graph.nodes.length > 1) {
|
||||
stats.pagesWithoutBacklinks++;
|
||||
issues.push(`No backlinks: "${node.title}" has no pages linking to it`);
|
||||
}
|
||||
}
|
||||
|
||||
return { stats, issues };
|
||||
}
|
||||
|
||||
/** Git auto-commit after processing */
|
||||
autoCommit(message?: string): boolean {
|
||||
try {
|
||||
const { execSync } = require("node:child_process");
|
||||
// Check if we're in a git repo
|
||||
execSync("git rev-parse --git-dir", { stdio: "ignore", cwd: this.baseDir });
|
||||
|
||||
const commitMsg = message ?? `familiar: auto-process inbox at ${new Date().toISOString().slice(0, 16)}`;
|
||||
execSync(`git add -A`, { cwd: this.baseDir });
|
||||
execSync(`git commit -m "${commitMsg}"`, { cwd: this.baseDir, stdio: "ignore" });
|
||||
|
||||
// Count changed files
|
||||
const diff = execSync("git diff --cached --name-only", { encoding: "utf-8", cwd: this.baseDir });
|
||||
const fileCount = diff.trim().split("\n").filter(Boolean).length;
|
||||
return fileCount > 0;
|
||||
} catch {
|
||||
return false; // Not a git repo or git not available
|
||||
}
|
||||
}
|
||||
|
||||
private slugify(text: string): string {
|
||||
return text
|
||||
.toLowerCase()
|
||||
|
|
@ -323,3 +394,15 @@ export interface GraphIndex {
|
|||
nodes: GraphNode[];
|
||||
edges: GraphEdge[];
|
||||
}
|
||||
|
||||
export interface GraphHealthReport {
|
||||
stats: {
|
||||
totalPages: number;
|
||||
totalConnections: number;
|
||||
orphanedPages: number;
|
||||
pagesWithLowConfidence: number;
|
||||
pagesWithoutBacklinks: number;
|
||||
stalePages: number;
|
||||
};
|
||||
issues: string[];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue