@tomingtoming/kioq 0.8.2 → 0.9.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/dist/src/engagement.js +130 -0
- package/dist/src/index.js +135 -400
- package/dist/src/noteStore.js +152 -0
- package/dist/src/signalLog.js +62 -0
- package/dist/src/storage/githubStorage.js +31 -5
- package/package.json +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const HUMAN_SIGNALS = new Set([
|
|
2
|
+
"human_cited",
|
|
3
|
+
"human_discussed",
|
|
4
|
+
"human_applied",
|
|
5
|
+
"human_questioned",
|
|
6
|
+
"human_dismissed",
|
|
7
|
+
]);
|
|
8
|
+
const SIGNAL_STRENGTH = {
|
|
9
|
+
search_hit: 1,
|
|
10
|
+
context_pulled: 2,
|
|
11
|
+
read: 3,
|
|
12
|
+
orient_discovered: 4,
|
|
13
|
+
orient_referenced: 5,
|
|
14
|
+
modified: 6,
|
|
15
|
+
human_questioned: 7,
|
|
16
|
+
human_cited: 8,
|
|
17
|
+
human_discussed: 9,
|
|
18
|
+
human_dismissed: 5,
|
|
19
|
+
human_applied: 10,
|
|
20
|
+
};
|
|
21
|
+
export class EngagementIndex {
|
|
22
|
+
facetsMap = new Map();
|
|
23
|
+
build(signals, notes) {
|
|
24
|
+
this.facetsMap.clear();
|
|
25
|
+
const notesByPermalink = new Map();
|
|
26
|
+
for (const note of notes) {
|
|
27
|
+
notesByPermalink.set(note.permalink, note);
|
|
28
|
+
}
|
|
29
|
+
// Initialize facets for all notes
|
|
30
|
+
for (const note of notes) {
|
|
31
|
+
this.facetsMap.set(note.permalink, this.emptyFacets(note.frontmatter));
|
|
32
|
+
}
|
|
33
|
+
// Process signal log
|
|
34
|
+
for (const signal of signals) {
|
|
35
|
+
const facets = this.facetsMap.get(signal.note);
|
|
36
|
+
if (!facets)
|
|
37
|
+
continue;
|
|
38
|
+
facets.contactCount++;
|
|
39
|
+
if (!facets.lastContact || signal.ts > facets.lastContact) {
|
|
40
|
+
facets.lastContact = signal.ts;
|
|
41
|
+
}
|
|
42
|
+
if (HUMAN_SIGNALS.has(signal.op)) {
|
|
43
|
+
facets.humanSignalCount++;
|
|
44
|
+
if (!facets.lastHumanSignal || signal.ts > facets.lastHumanSignal) {
|
|
45
|
+
facets.lastHumanSignal = signal.ts;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!facets.dominantSignal || SIGNAL_STRENGTH[signal.op] > SIGNAL_STRENGTH[facets.dominantSignal]) {
|
|
49
|
+
facets.dominantSignal = signal.op;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Compute ai_reference_count and synthesis_count from note content
|
|
53
|
+
const wikiLinkPattern = /\[\[([^\]\n]+)\]\]/g;
|
|
54
|
+
for (const note of notes) {
|
|
55
|
+
const origin = safeString(note.frontmatter.origin);
|
|
56
|
+
if (origin !== "ai" && origin !== "collaborative")
|
|
57
|
+
continue;
|
|
58
|
+
const links = new Set();
|
|
59
|
+
let match;
|
|
60
|
+
while ((match = wikiLinkPattern.exec(note.body)) !== null) {
|
|
61
|
+
const target = match[1].split("|")[0].trim();
|
|
62
|
+
links.add(target);
|
|
63
|
+
}
|
|
64
|
+
wikiLinkPattern.lastIndex = 0;
|
|
65
|
+
for (const target of links) {
|
|
66
|
+
// Try to resolve the target to a permalink
|
|
67
|
+
const resolved = this.resolveTarget(target, notes, notesByPermalink);
|
|
68
|
+
if (resolved) {
|
|
69
|
+
const targetFacets = this.facetsMap.get(resolved);
|
|
70
|
+
if (targetFacets) {
|
|
71
|
+
targetFacets.aiReferenceCount++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Check if this note is a synthesis (has parent or source links)
|
|
76
|
+
const parentMatch = /^-\s*(?:親|parent):\s*\[\[([^\]]+)\]\]/im.exec(note.body);
|
|
77
|
+
if (parentMatch && (origin === "ai" || origin === "collaborative")) {
|
|
78
|
+
const parentTarget = parentMatch[1].split("|")[0].trim();
|
|
79
|
+
const resolved = this.resolveTarget(parentTarget, notes, notesByPermalink);
|
|
80
|
+
if (resolved) {
|
|
81
|
+
const parentFacets = this.facetsMap.get(resolved);
|
|
82
|
+
if (parentFacets) {
|
|
83
|
+
parentFacets.synthesisCount++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
get(permalink) {
|
|
90
|
+
return this.facetsMap.get(permalink);
|
|
91
|
+
}
|
|
92
|
+
getAll() {
|
|
93
|
+
return this.facetsMap;
|
|
94
|
+
}
|
|
95
|
+
resolveTarget(target, notes, byPermalink) {
|
|
96
|
+
// Direct permalink match
|
|
97
|
+
if (byPermalink.has(target))
|
|
98
|
+
return target;
|
|
99
|
+
// Title match (case-insensitive)
|
|
100
|
+
const lowerTarget = target.toLowerCase();
|
|
101
|
+
for (const note of notes) {
|
|
102
|
+
if (note.title.toLowerCase() === lowerTarget)
|
|
103
|
+
return note.permalink;
|
|
104
|
+
if (note.permalink.toLowerCase() === lowerTarget)
|
|
105
|
+
return note.permalink;
|
|
106
|
+
const basename = note.permalink.split("/").pop()?.toLowerCase();
|
|
107
|
+
if (basename === lowerTarget)
|
|
108
|
+
return note.permalink;
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
emptyFacets(frontmatter) {
|
|
113
|
+
return {
|
|
114
|
+
contactCount: 0,
|
|
115
|
+
lastContact: null,
|
|
116
|
+
humanSignalCount: 0,
|
|
117
|
+
lastHumanSignal: null,
|
|
118
|
+
dominantSignal: null,
|
|
119
|
+
aiReferenceCount: 0,
|
|
120
|
+
synthesisCount: 0,
|
|
121
|
+
origin: safeString(frontmatter.origin),
|
|
122
|
+
createdVia: safeString(frontmatter.created_via),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function safeString(value) {
|
|
127
|
+
if (typeof value === "string" && value.length > 0)
|
|
128
|
+
return value;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -3,13 +3,14 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { lintStructureResponseModeHint, lintStructureResponsePlan, summarizeLintStructureResponse, } from "./auditResponseSummary.js";
|
|
5
5
|
import { loadConfig } from "./config.js";
|
|
6
|
-
import { memoryContractResponseModeHint, memoryContractResponsePlan, summarizeMemoryContractResponse, } from "./contractResponseSummary.js";
|
|
7
6
|
import { summarizeNoConflict } from "./conflictSummary.js";
|
|
8
7
|
import { LocalNoteStore } from "./noteStore.js";
|
|
9
8
|
import { GitHubStorage, LocalFsStorage } from "./storage/index.js";
|
|
9
|
+
import { SignalLog } from "./signalLog.js";
|
|
10
|
+
import { EngagementIndex } from "./engagement.js";
|
|
10
11
|
import { normalizeDateInput, preview } from "./normalizers.js";
|
|
11
12
|
import { summarizeDeleteImpact, summarizeRenameImpact } from "./operationImpact.js";
|
|
12
|
-
import {
|
|
13
|
+
import { summarizeReadNoteResponse, summarizeSearchResponse, } from "./readResponseSummary.js";
|
|
13
14
|
import { summarizeLintChangeTrigger, summarizeReadChangeTrigger } from "./changeTriggerSummary.js";
|
|
14
15
|
import { RESPONSE_MODE_VALUES, normalizeResponseMode, responseModeAtLeast } from "./responseMode.js";
|
|
15
16
|
import { TOOL_SCOPE_LABELS, responsibilityWarningCode } from "./toolScope.js";
|
|
@@ -288,6 +289,8 @@ async function main() {
|
|
|
288
289
|
: new LocalFsStorage(config.root);
|
|
289
290
|
await storage.ensureRoot();
|
|
290
291
|
const store = new LocalNoteStore(config, storage);
|
|
292
|
+
const signalLog = new SignalLog(storage);
|
|
293
|
+
const engagementIndex = new EngagementIndex();
|
|
291
294
|
const appendStorageContext = (lines) => {
|
|
292
295
|
lines.push(`storage_backend: ${config.github ? "github" : "filesystem"}`);
|
|
293
296
|
lines.push(`storage_root: ${config.root}`);
|
|
@@ -312,56 +315,65 @@ async function main() {
|
|
|
312
315
|
name: "kioq",
|
|
313
316
|
version: "0.7.0",
|
|
314
317
|
});
|
|
315
|
-
server.tool("
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
firstIndexCandidateTitle: result.indexNoteCandidates[0]?.title,
|
|
318
|
+
server.tool("orient", "kioqを通じて思考を構造化するツール。チェインバー型パイロット支援システムとして、応答を組み立てる前に現在の思考を渡すことで、3つのスロットを返します: (1) relevant — 思考に直接関連するノート(engagement重み付け)、(2) active_context — 進行中のflowと最近のアクティブなノート、(3) discovery — 未接触だが接点がありそうなノート(セレンディピティ)。各ノートのengagement(接触回数、人間の反応、AI被参照数、origin)が付与されます。observationで人間の反応を記録でき、それがengagementに蓄積されます。対話の各ターンで呼び出すことを推奨します。", {
|
|
319
|
+
thought: z.string().min(1).describe("今何を考えているか、何について応答しようとしているか"),
|
|
320
|
+
references: z.array(z.string()).optional().describe("明示的に関連するノートの identifier(あれば)"),
|
|
321
|
+
observation: z.object({
|
|
322
|
+
note: z.string().min(1).describe("観察対象のノート identifier"),
|
|
323
|
+
signal: z.enum(["cited", "discussed", "applied", "questioned", "dismissed"]).describe("人間の反応の種類"),
|
|
324
|
+
}).optional().describe("直前の対話で人間がノートの概念に反応した場合に記録"),
|
|
325
|
+
}, async ({ thought, references, observation }) => {
|
|
326
|
+
const signals = await signalLog.load();
|
|
327
|
+
const notes = await store.loadAllNotes();
|
|
328
|
+
engagementIndex.build(signals, notes);
|
|
329
|
+
const result = await store.orient({
|
|
330
|
+
thought,
|
|
331
|
+
references,
|
|
332
|
+
observation,
|
|
333
|
+
engagementIndex,
|
|
334
|
+
signalLog,
|
|
333
335
|
});
|
|
336
|
+
await signalLog.flush();
|
|
334
337
|
const lines = [];
|
|
335
|
-
lines.push(
|
|
338
|
+
lines.push(`project: ${result.project}`);
|
|
336
339
|
appendStorageContext(lines);
|
|
337
|
-
|
|
338
|
-
lines.push(`
|
|
339
|
-
appendReadResponseSummary(lines, summary);
|
|
340
|
-
lines.push(`件数: ${result.results.length}`);
|
|
340
|
+
lines.push(`scope_label: orient`);
|
|
341
|
+
lines.push(`thought: ${thought.slice(0, 200)}`);
|
|
341
342
|
lines.push("");
|
|
342
|
-
if (result.
|
|
343
|
-
lines.push("
|
|
343
|
+
if (result.relevant.length > 0) {
|
|
344
|
+
lines.push("--- relevant ---");
|
|
345
|
+
for (const item of result.relevant) {
|
|
346
|
+
lines.push(`- ${item.title}`);
|
|
347
|
+
lines.push(` permalink: ${item.permalink}`);
|
|
348
|
+
lines.push(` reason: ${item.reason}`);
|
|
349
|
+
lines.push(` engagement: contact=${item.engagement.contactCount} human=${item.engagement.humanSignalCount} ai_ref=${item.engagement.aiReferenceCount} origin=${item.engagement.origin ?? "unknown"} created_via=${item.engagement.createdVia ?? "unknown"}`);
|
|
350
|
+
lines.push(` snippet: ${item.snippet.slice(0, 200)}`);
|
|
351
|
+
}
|
|
352
|
+
lines.push("");
|
|
344
353
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
lines.push(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (responseModeAtLeast(responseMode, "verbose")) {
|
|
355
|
-
lines.push(` index_like_reasons: ${item.indexLike && item.indexReasons.length > 0 ? item.indexReasons.join(", ") : "(none)"}`);
|
|
356
|
-
}
|
|
357
|
-
});
|
|
354
|
+
if (result.activeContext.length > 0) {
|
|
355
|
+
lines.push("--- active_context ---");
|
|
356
|
+
for (const item of result.activeContext) {
|
|
357
|
+
lines.push(`- ${item.title}`);
|
|
358
|
+
lines.push(` permalink: ${item.permalink}`);
|
|
359
|
+
lines.push(` reason: ${item.reason}`);
|
|
360
|
+
lines.push(` engagement: contact=${item.engagement.contactCount} human=${item.engagement.humanSignalCount} origin=${item.engagement.origin ?? "unknown"}`);
|
|
361
|
+
}
|
|
362
|
+
lines.push("");
|
|
358
363
|
}
|
|
359
|
-
if (
|
|
364
|
+
if (result.discovery.length > 0) {
|
|
365
|
+
lines.push("--- discovery ---");
|
|
366
|
+
for (const item of result.discovery) {
|
|
367
|
+
lines.push(`- ${item.title}`);
|
|
368
|
+
lines.push(` permalink: ${item.permalink}`);
|
|
369
|
+
lines.push(` reason: ${item.reason}`);
|
|
370
|
+
lines.push(` engagement: contact=${item.engagement.contactCount} origin=${item.engagement.origin ?? "unknown"} created_via=${item.engagement.createdVia ?? "unknown"}`);
|
|
371
|
+
lines.push(` snippet: ${item.snippet.slice(0, 160)}`);
|
|
372
|
+
}
|
|
360
373
|
lines.push("");
|
|
361
|
-
appendIndexNoteCandidates(lines, result.indexNoteCandidates, "recommended_indexes");
|
|
362
374
|
}
|
|
363
|
-
if (
|
|
364
|
-
|
|
375
|
+
if (result.relevant.length === 0 && result.discovery.length === 0) {
|
|
376
|
+
lines.push("関連するノートは見つかりませんでした。");
|
|
365
377
|
}
|
|
366
378
|
return textResult(lines.join("\n"));
|
|
367
379
|
});
|
|
@@ -522,279 +534,7 @@ async function main() {
|
|
|
522
534
|
return textResult(lines.join("\n"));
|
|
523
535
|
}
|
|
524
536
|
});
|
|
525
|
-
server.tool("
|
|
526
|
-
identifier: z.string().min(1),
|
|
527
|
-
response_mode: responseModeSchema,
|
|
528
|
-
}, async ({ identifier, response_mode }) => {
|
|
529
|
-
const responseMode = normalizeResponseMode(response_mode);
|
|
530
|
-
const result = await store.resolveLinks({
|
|
531
|
-
identifier,
|
|
532
|
-
});
|
|
533
|
-
const unresolvedLinkCount = result.links.filter((link) => !link.resolved).length;
|
|
534
|
-
const summary = summarizeResolveLinksResponse({
|
|
535
|
-
indexLike: result.indexLike,
|
|
536
|
-
unresolvedLinkCount,
|
|
537
|
-
boundaryWarningCount: result.boundaryWarnings.length,
|
|
538
|
-
indexCandidateTitle: result.indexNoteCandidates[0]?.title,
|
|
539
|
-
});
|
|
540
|
-
const lines = [];
|
|
541
|
-
lines.push(`identifier: ${identifier}`);
|
|
542
|
-
lines.push(`project: ${result.project}`);
|
|
543
|
-
appendStorageContext(lines);
|
|
544
|
-
appendScopeLabel(lines, TOOL_SCOPE_LABELS.resolve_links);
|
|
545
|
-
lines.push(`response_mode: ${responseMode}`);
|
|
546
|
-
appendReadResponseSummary(lines, summary);
|
|
547
|
-
appendResponsibilityWarning(lines, result.boundaryWarnings);
|
|
548
|
-
lines.push(`file: ${result.note.relativePath}`);
|
|
549
|
-
lines.push(`wikilinks: ${result.links.length}`);
|
|
550
|
-
if (responseModeAtLeast(responseMode, "verbose")) {
|
|
551
|
-
appendIndexLike(lines, result.indexLike, result.indexReasons);
|
|
552
|
-
}
|
|
553
|
-
appendBoundaryWarnings(lines, result.boundaryWarnings);
|
|
554
|
-
lines.push("");
|
|
555
|
-
if (result.links.length === 0) {
|
|
556
|
-
lines.push("WikiLink は見つかりませんでした。");
|
|
557
|
-
}
|
|
558
|
-
else {
|
|
559
|
-
result.links.forEach((link, index) => {
|
|
560
|
-
lines.push(`${index + 1}. ${link.raw}`);
|
|
561
|
-
if (link.resolved) {
|
|
562
|
-
lines.push(` resolved: yes`);
|
|
563
|
-
lines.push(` to: ${link.resolvedTitle} (${link.resolvedFilePath})`);
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
lines.push(` resolved: no`);
|
|
567
|
-
lines.push(` reason: ${link.reason ?? "unknown"}`);
|
|
568
|
-
lines.push(` target: ${link.target}`);
|
|
569
|
-
}
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
if (responseModeAtLeast(responseMode, "verbose")) {
|
|
573
|
-
lines.push("");
|
|
574
|
-
appendIndexNoteCandidates(lines, result.indexNoteCandidates);
|
|
575
|
-
}
|
|
576
|
-
return textResult(lines.join("\n"));
|
|
577
|
-
});
|
|
578
|
-
server.tool("list_backlinks", "指定ノートへのバックリンク一覧を返します。", {
|
|
579
|
-
identifier: z.string().min(1),
|
|
580
|
-
limit: z.number().int().min(1).max(100).optional().describe("最大件数 (default: 20)"),
|
|
581
|
-
response_mode: responseModeSchema,
|
|
582
|
-
}, async ({ identifier, limit, response_mode }) => {
|
|
583
|
-
const responseMode = normalizeResponseMode(response_mode);
|
|
584
|
-
const result = await store.listBacklinks({
|
|
585
|
-
identifier,
|
|
586
|
-
limit: limit ?? 20,
|
|
587
|
-
});
|
|
588
|
-
const summary = summarizeBacklinksResponse({
|
|
589
|
-
indexLike: result.indexLike,
|
|
590
|
-
backlinkCount: result.backlinks.length,
|
|
591
|
-
boundaryWarningCount: result.boundaryWarnings.length,
|
|
592
|
-
firstBacklinkTitle: result.backlinks[0]?.title,
|
|
593
|
-
indexCandidateTitle: result.indexNoteCandidates[0]?.title,
|
|
594
|
-
});
|
|
595
|
-
const lines = [];
|
|
596
|
-
lines.push(`target: ${result.target.title}`);
|
|
597
|
-
lines.push(`project: ${result.project}`);
|
|
598
|
-
appendStorageContext(lines);
|
|
599
|
-
appendScopeLabel(lines, TOOL_SCOPE_LABELS.list_backlinks);
|
|
600
|
-
lines.push(`response_mode: ${responseMode}`);
|
|
601
|
-
appendReadResponseSummary(lines, summary);
|
|
602
|
-
appendResponsibilityWarning(lines, result.boundaryWarnings);
|
|
603
|
-
if (responseModeAtLeast(responseMode, "verbose")) {
|
|
604
|
-
appendIndexLike(lines, result.indexLike, result.indexReasons, "target");
|
|
605
|
-
}
|
|
606
|
-
appendBoundaryWarnings(lines, result.boundaryWarnings);
|
|
607
|
-
lines.push(`backlinks: ${result.backlinks.length}`);
|
|
608
|
-
lines.push("");
|
|
609
|
-
if (result.backlinks.length === 0) {
|
|
610
|
-
lines.push("バックリンクは見つかりませんでした。");
|
|
611
|
-
}
|
|
612
|
-
else {
|
|
613
|
-
result.backlinks.forEach((item, index) => {
|
|
614
|
-
lines.push(`${index + 1}. ${item.title}`);
|
|
615
|
-
lines.push(` count: ${item.count}`);
|
|
616
|
-
if (responseModeAtLeast(responseMode, "standard")) {
|
|
617
|
-
lines.push(` file: ${item.filePath}`);
|
|
618
|
-
lines.push(` updated: ${item.updated}`);
|
|
619
|
-
}
|
|
620
|
-
item.examples.forEach((example) => {
|
|
621
|
-
lines.push(` example: ${example}`);
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
if (responseModeAtLeast(responseMode, "verbose")) {
|
|
626
|
-
lines.push("");
|
|
627
|
-
appendIndexNoteCandidates(lines, result.indexNoteCandidates);
|
|
628
|
-
}
|
|
629
|
-
return textResult(lines.join("\n"));
|
|
630
|
-
});
|
|
631
|
-
server.tool("context_bundle", "指定ノートを中心に、関連ノートを優先度付きで返します。", {
|
|
632
|
-
identifier: z.string().min(1),
|
|
633
|
-
limit: z.number().int().min(1).max(30).optional().describe("最大件数 (default: 8)"),
|
|
634
|
-
response_mode: responseModeSchema,
|
|
635
|
-
}, async ({ identifier, limit, response_mode }) => {
|
|
636
|
-
const responseMode = normalizeResponseMode(response_mode);
|
|
637
|
-
const result = await store.contextBundle({
|
|
638
|
-
identifier,
|
|
639
|
-
limit: limit ?? 8,
|
|
640
|
-
});
|
|
641
|
-
const summary = summarizeContextBundleResponse({
|
|
642
|
-
sourceIndexLike: result.source.indexLike,
|
|
643
|
-
bundleItemCount: result.items.length,
|
|
644
|
-
boundaryWarningCount: result.source.boundaryWarnings.length,
|
|
645
|
-
firstBundleItemTitle: result.items[0]?.title,
|
|
646
|
-
indexCandidateTitle: result.indexNoteCandidates[0]?.title,
|
|
647
|
-
});
|
|
648
|
-
const lines = [];
|
|
649
|
-
lines.push(`source: ${result.source.title}`);
|
|
650
|
-
lines.push(`project: ${result.project}`);
|
|
651
|
-
appendStorageContext(lines);
|
|
652
|
-
appendScopeLabel(lines, TOOL_SCOPE_LABELS.context_bundle);
|
|
653
|
-
lines.push(`response_mode: ${responseMode}`);
|
|
654
|
-
appendReadResponseSummary(lines, summary);
|
|
655
|
-
appendResponsibilityWarning(lines, result.source.boundaryWarnings);
|
|
656
|
-
if (responseModeAtLeast(responseMode, "standard")) {
|
|
657
|
-
lines.push(`memory_kind: ${result.source.memoryKind}`);
|
|
658
|
-
lines.push(`file: ${result.source.filePath}`);
|
|
659
|
-
}
|
|
660
|
-
if (responseModeAtLeast(responseMode, "verbose")) {
|
|
661
|
-
appendIndexLike(lines, result.source.indexLike, result.source.indexReasons, "source");
|
|
662
|
-
}
|
|
663
|
-
appendBoundaryWarnings(lines, result.source.boundaryWarnings);
|
|
664
|
-
lines.push(`bundle_items: ${result.items.length}`);
|
|
665
|
-
lines.push("");
|
|
666
|
-
if (result.items.length === 0) {
|
|
667
|
-
lines.push("関連ノートは見つかりませんでした。");
|
|
668
|
-
}
|
|
669
|
-
else {
|
|
670
|
-
result.items.forEach((item, index) => {
|
|
671
|
-
lines.push(`${index + 1}. ${item.title}`);
|
|
672
|
-
lines.push(` score: ${item.score}`);
|
|
673
|
-
if (responseModeAtLeast(responseMode, "standard")) {
|
|
674
|
-
lines.push(` memory_kind: ${item.memoryKind}`);
|
|
675
|
-
lines.push(` file: ${item.filePath}`);
|
|
676
|
-
}
|
|
677
|
-
if (responseModeAtLeast(responseMode, "verbose")) {
|
|
678
|
-
lines.push(` reasons: ${item.reasons.join(", ")}`);
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
if (responseModeAtLeast(responseMode, "verbose")) {
|
|
683
|
-
lines.push("");
|
|
684
|
-
appendIndexNoteCandidates(lines, result.indexNoteCandidates);
|
|
685
|
-
}
|
|
686
|
-
if (responseModeAtLeast(responseMode, "standard")) {
|
|
687
|
-
appendExplorationGuidance(lines, result.explorationGuidance);
|
|
688
|
-
}
|
|
689
|
-
return textResult(lines.join("\n"));
|
|
690
|
-
});
|
|
691
|
-
server.tool("memory_contract", "kioq の Memory Contract(構造化ルール)を返します。", {
|
|
692
|
-
response_mode: responseModeSchema,
|
|
693
|
-
}, async ({ response_mode }) => {
|
|
694
|
-
const responseMode = normalizeResponseMode(response_mode);
|
|
695
|
-
const plan = memoryContractResponsePlan(responseMode);
|
|
696
|
-
const summary = {
|
|
697
|
-
...summarizeMemoryContractResponse(),
|
|
698
|
-
...memoryContractResponseModeHint(responseMode),
|
|
699
|
-
};
|
|
700
|
-
const contract = store.getMemoryContract();
|
|
701
|
-
const lines = [];
|
|
702
|
-
appendStorageContext(lines);
|
|
703
|
-
appendScopeLabel(lines, TOOL_SCOPE_LABELS.memory_contract);
|
|
704
|
-
lines.push(`response_mode: ${responseMode}`);
|
|
705
|
-
appendAuditResponseSummary(lines, summary);
|
|
706
|
-
lines.push(`memory_contract_version: ${contract.version}`);
|
|
707
|
-
lines.push(`required_frontmatter: ${contract.requiredFrontmatter.join(", ")}`);
|
|
708
|
-
lines.push(`flow_required_frontmatter: ${contract.flowRequiredFrontmatter.join(", ")}`);
|
|
709
|
-
lines.push(`parent_markers: ${contract.parentLinkMarkers.join(", ")}`);
|
|
710
|
-
lines.push(`source_metadata_canonical_field: ${contract.optionalSourceMetadata.canonicalField}`);
|
|
711
|
-
lines.push(`source_metadata_auxiliary_fields: ${contract.optionalSourceMetadata.auxiliaryFields.join(", ")}`);
|
|
712
|
-
lines.push(`index_threshold_score_at_least: ${contract.indexNavigationPolicy.thresholds.scoreAtLeast}`);
|
|
713
|
-
lines.push(`index_threshold_index_sections_at_least: ${contract.indexNavigationPolicy.thresholds.indexSectionsAtLeast}`);
|
|
714
|
-
lines.push(`technical_debt_threshold_stale_flow_days: ${contract.technicalDebtPolicy.thresholds.staleFlowDays}`);
|
|
715
|
-
lines.push("sample_templates_available: stock, flow, index");
|
|
716
|
-
if (!plan.includePolicyRules) {
|
|
717
|
-
return textResult(lines.join("\n"));
|
|
718
|
-
}
|
|
719
|
-
lines.push("requirements:");
|
|
720
|
-
lines.push(`- min_resolved_links: ${contract.requirements.minResolvedLinks}`);
|
|
721
|
-
lines.push(`- min_backlinks: ${contract.requirements.minBacklinks}`);
|
|
722
|
-
lines.push(`- min_parent_links: ${contract.requirements.minParentLinks}`);
|
|
723
|
-
lines.push(`- min_related_links: ${contract.requirements.minRelatedLinks}`);
|
|
724
|
-
lines.push(`- max_unresolved_links: ${contract.requirements.maxUnresolvedLinks}`);
|
|
725
|
-
lines.push(`- min_tags: ${contract.requirements.minTags}`);
|
|
726
|
-
lines.push("flow_requirements:");
|
|
727
|
-
lines.push(`- min_resolved_links: ${contract.flowRequirements.minResolvedLinks}`);
|
|
728
|
-
lines.push(`- min_backlinks: ${contract.flowRequirements.minBacklinks}`);
|
|
729
|
-
lines.push(`- min_parent_links: ${contract.flowRequirements.minParentLinks}`);
|
|
730
|
-
lines.push(`- min_related_links: ${contract.flowRequirements.minRelatedLinks}`);
|
|
731
|
-
lines.push(`- max_unresolved_links: ${contract.flowRequirements.maxUnresolvedLinks}`);
|
|
732
|
-
lines.push(`- min_tags: ${contract.flowRequirements.minTags}`);
|
|
733
|
-
lines.push("optional_source_metadata:");
|
|
734
|
-
lines.push(`- canonical_field: ${contract.optionalSourceMetadata.canonicalField}`);
|
|
735
|
-
lines.push(`- auxiliary_fields: ${contract.optionalSourceMetadata.auxiliaryFields.join(", ")}`);
|
|
736
|
-
contract.optionalSourceMetadata.rules.forEach((rule) => {
|
|
737
|
-
lines.push(`- rule: ${rule}`);
|
|
738
|
-
});
|
|
739
|
-
lines.push("index_navigation_policy:");
|
|
740
|
-
lines.push(`- excluded_memory_kinds: ${contract.indexNavigationPolicy.excludedMemoryKinds.join(", ")}`);
|
|
741
|
-
lines.push(`- threshold_score_at_least: ${contract.indexNavigationPolicy.thresholds.scoreAtLeast}`);
|
|
742
|
-
lines.push(`- threshold_index_sections_at_least: ${contract.indexNavigationPolicy.thresholds.indexSectionsAtLeast}`);
|
|
743
|
-
lines.push(`- configurable_via_env: ${contract.indexNavigationPolicy.configurableVia.env.join(", ")}`);
|
|
744
|
-
lines.push(`- configurable_via_cli: ${contract.indexNavigationPolicy.configurableVia.cli.join(", ")}`);
|
|
745
|
-
if (plan.includePolicySignals) {
|
|
746
|
-
contract.indexNavigationPolicy.signals.forEach((signal) => {
|
|
747
|
-
lines.push(`- signal: ${signal.code} weight=${signal.weight} description=${signal.description}`);
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
contract.indexNavigationPolicy.rules.forEach((rule) => {
|
|
751
|
-
lines.push(`- rule: ${rule}`);
|
|
752
|
-
});
|
|
753
|
-
lines.push("technical_debt_policy:");
|
|
754
|
-
lines.push(`- signals: ${contract.technicalDebtPolicy.signals.join(", ")}`);
|
|
755
|
-
lines.push(`- stale_flow_states: ${contract.technicalDebtPolicy.staleFlowStates.join(", ")}`);
|
|
756
|
-
lines.push(`- threshold_stale_flow_days: ${contract.technicalDebtPolicy.thresholds.staleFlowDays}`);
|
|
757
|
-
lines.push(`- stale_flow_age_bucket_near_threshold: ${contract.technicalDebtPolicy.staleFlowAgeBuckets.nearThreshold}`);
|
|
758
|
-
lines.push(`- stale_flow_age_bucket_aging: ${contract.technicalDebtPolicy.staleFlowAgeBuckets.aging}`);
|
|
759
|
-
lines.push(`- stale_flow_age_bucket_long_stale: ${contract.technicalDebtPolicy.staleFlowAgeBuckets.longStale}`);
|
|
760
|
-
lines.push(`- cleanup_ratio_definition: ${contract.technicalDebtPolicy.cleanupRatioDefinition}`);
|
|
761
|
-
lines.push(`- cleanup_ready_count_definition: ${contract.technicalDebtPolicy.cleanupReadyCountDefinition}`);
|
|
762
|
-
lines.push(`- attention_note_count_definition: ${contract.technicalDebtPolicy.attentionNoteCountDefinition}`);
|
|
763
|
-
lines.push(`- dependency_direction_violation_count_definition: ${contract.technicalDebtPolicy.dependencyDirectionViolationCountDefinition}`);
|
|
764
|
-
lines.push(`- single_use_tag_candidate_count_definition: ${contract.technicalDebtPolicy.singleUseTagCandidateCountDefinition}`);
|
|
765
|
-
lines.push(`- title_body_mismatch_candidate_count_definition: ${contract.technicalDebtPolicy.titleBodyMismatchCandidateCountDefinition}`);
|
|
766
|
-
lines.push(`- unresolved_wikilinks_delta_status: ${contract.technicalDebtPolicy.unresolvedWikilinksDeltaStatus}`);
|
|
767
|
-
lines.push(`- configurable_via_env: ${contract.technicalDebtPolicy.configurableVia.env.join(", ")}`);
|
|
768
|
-
lines.push(`- configurable_via_cli: ${contract.technicalDebtPolicy.configurableVia.cli.join(", ")}`);
|
|
769
|
-
lines.push("response_mode_policy:");
|
|
770
|
-
lines.push(`- supported_read_tools: ${contract.responseModePolicy.supportedTools.read.join(", ")}`);
|
|
771
|
-
lines.push(`- supported_audit_tools: ${contract.responseModePolicy.supportedTools.audit.join(", ")}`);
|
|
772
|
-
lines.push(`- minimal: ${contract.responseModePolicy.modes.minimal}`);
|
|
773
|
-
lines.push(`- standard: ${contract.responseModePolicy.modes.standard}`);
|
|
774
|
-
lines.push(`- verbose: ${contract.responseModePolicy.modes.verbose}`);
|
|
775
|
-
contract.responseModePolicy.rules.forEach((rule) => {
|
|
776
|
-
lines.push(`- rule: ${rule}`);
|
|
777
|
-
});
|
|
778
|
-
if (plan.includeSampleTemplates) {
|
|
779
|
-
lines.push("");
|
|
780
|
-
lines.push("sample_template_stock:");
|
|
781
|
-
lines.push("```md");
|
|
782
|
-
contract.sampleTemplates.stock.forEach((line) => lines.push(line));
|
|
783
|
-
lines.push("```");
|
|
784
|
-
lines.push("");
|
|
785
|
-
lines.push("sample_template_flow:");
|
|
786
|
-
lines.push("```md");
|
|
787
|
-
contract.sampleTemplates.flow.forEach((line) => lines.push(line));
|
|
788
|
-
lines.push("```");
|
|
789
|
-
lines.push("");
|
|
790
|
-
lines.push("sample_template_index:");
|
|
791
|
-
lines.push("```md");
|
|
792
|
-
contract.sampleTemplates.index.forEach((line) => lines.push(line));
|
|
793
|
-
lines.push("```");
|
|
794
|
-
}
|
|
795
|
-
return textResult(lines.join("\n"));
|
|
796
|
-
});
|
|
797
|
-
server.tool("lint_structure", "構造化を促すために、ノート群の問題を診断して優先順位付きで返します。", {
|
|
537
|
+
server.tool("lint_structure", "ノート群の構造的問題を診断し、優先順位付きで返します。", {
|
|
798
538
|
limit: z.number().int().min(1).max(200).optional().describe("返却する issue 最大件数 (default: 50)"),
|
|
799
539
|
response_mode: responseModeSchema,
|
|
800
540
|
}, async ({ limit, response_mode }) => {
|
|
@@ -914,19 +654,92 @@ async function main() {
|
|
|
914
654
|
}
|
|
915
655
|
return textResult(lines.join("\n"));
|
|
916
656
|
});
|
|
917
|
-
server.tool("write_note", "
|
|
657
|
+
server.tool("write_note", "ノートを作成/更新します。note_type='flow' で進行中の問いを記録、それ以外で知識(stock)を記録します。origin で誰が起点かを記録し、engagement追跡の基盤になります。", {
|
|
918
658
|
title: z.string().min(1),
|
|
919
|
-
content: z.string().min(1),
|
|
920
|
-
directory: z.string().min(1).optional().describe("default: KIOQ_DEFAULT_DIRECTORY
|
|
659
|
+
content: z.string().min(1).describe("note_type='flow' の場合は ## Notes セクションの本文"),
|
|
660
|
+
directory: z.string().min(1).optional().describe("default: stock→KIOQ_DEFAULT_DIRECTORY, flow→Flow"),
|
|
921
661
|
tags: z.array(z.string()).optional(),
|
|
922
|
-
note_type: z.string().optional().describe("
|
|
923
|
-
|
|
662
|
+
note_type: z.string().optional().describe("stock(default) / flow / note / decision / task"),
|
|
663
|
+
origin: z.enum(["human", "ai", "collaborative"]).optional().describe("誰が起点か (human=体験, ai=AI生成/抽出, collaborative=対話から)"),
|
|
664
|
+
created_via: z.enum(["experience", "dialogue", "extraction", "curation", "capture"]).optional().describe("生成経路"),
|
|
665
|
+
human_role: z.enum(["thinker", "architect", "curator", "reviewer"]).optional().describe("collaborative 時の人間の役割"),
|
|
666
|
+
question: z.string().optional().describe("flow 時の問い (note_type='flow' で必須)"),
|
|
667
|
+
next_action: z.string().optional().describe("flow 時の次のアクション (note_type='flow' で必須)"),
|
|
668
|
+
parent_stock: z.string().optional().describe("flow 時の親 stock identifier (note_type='flow' で必須)"),
|
|
669
|
+
related: z.array(z.string()).optional().describe("flow 時の追加関連リンク先"),
|
|
670
|
+
flow_state: z.string().optional().describe("flow 時の状態: capture / active / blocked / done / promoted / dropped"),
|
|
671
|
+
}, async ({ title, content, directory, tags, note_type, origin, created_via, human_role, question, next_action, parent_stock, related, flow_state }) => {
|
|
672
|
+
// Flow note path
|
|
673
|
+
if (note_type === "flow") {
|
|
674
|
+
if (!question || !next_action || !parent_stock) {
|
|
675
|
+
return textResult("error: note_type='flow' requires question, next_action, parent_stock");
|
|
676
|
+
}
|
|
677
|
+
const flowResult = await store.writeFlowNote({
|
|
678
|
+
title,
|
|
679
|
+
question,
|
|
680
|
+
nextAction: next_action,
|
|
681
|
+
parentStock: parent_stock,
|
|
682
|
+
related,
|
|
683
|
+
details: content,
|
|
684
|
+
directory,
|
|
685
|
+
tags,
|
|
686
|
+
flowState: flow_state,
|
|
687
|
+
origin,
|
|
688
|
+
createdVia: created_via,
|
|
689
|
+
humanRole: human_role,
|
|
690
|
+
});
|
|
691
|
+
const flowSummary = summarizeWriteFlowResponse({
|
|
692
|
+
operation: flowResult.operation,
|
|
693
|
+
unresolvedWikiLinkCount: flowResult.unresolvedWikiLinks.length,
|
|
694
|
+
boundaryWarningCount: flowResult.boundaryWarnings.length,
|
|
695
|
+
memoryContractStatus: flowResult.memoryContract.status,
|
|
696
|
+
duplicateWarningCount: flowResult.duplicateWarnings.length,
|
|
697
|
+
orphanWarning: flowResult.orphanWarning,
|
|
698
|
+
title: flowResult.title,
|
|
699
|
+
parentStockResolved: flowResult.parentStockResolved,
|
|
700
|
+
parentStock: flowResult.parentStock,
|
|
701
|
+
});
|
|
702
|
+
const flowLines = [
|
|
703
|
+
"flow ノートを作成/更新しました。",
|
|
704
|
+
`project: ${flowResult.project}`,
|
|
705
|
+
`scope_label: ${TOOL_SCOPE_LABELS.write_flow_note}`,
|
|
706
|
+
`primary_navigation_signal: ${flowSummary.primaryNavigationSignal}`,
|
|
707
|
+
`primary_quality_signal: ${flowSummary.primaryQualitySignal}`,
|
|
708
|
+
`next_recommended_action: ${flowSummary.nextRecommendedAction}`,
|
|
709
|
+
`operation: ${flowResult.operation}`,
|
|
710
|
+
`file: ${flowResult.relativePath}`,
|
|
711
|
+
`permalink: ${flowResult.permalink}`,
|
|
712
|
+
`title: ${flowResult.title}`,
|
|
713
|
+
`flow_state: ${flowResult.flowState}`,
|
|
714
|
+
`parent_stock: ${flowResult.parentStock}`,
|
|
715
|
+
`parent_stock_resolved: ${flowResult.parentStockResolved ? "yes" : "no"}`,
|
|
716
|
+
`link_health: ${flowResult.linkHealth.resolved}/${flowResult.linkHealth.total} resolved`,
|
|
717
|
+
`backlinks: ${flowResult.backlinkCount}`,
|
|
718
|
+
`orphan_warning: ${flowResult.orphanWarning ? "yes" : "no"}`,
|
|
719
|
+
];
|
|
720
|
+
flowLines.splice(2, 0, ...[`storage_backend: ${config.github ? "github" : "filesystem"}`, `storage_root: ${config.root}`, ...(config.github ? [`storage_repo: ${config.github.owner}/${config.github.repo}`, `storage_branch: ${config.github.branch}`] : [])]);
|
|
721
|
+
if (flowSummary.nextRecommendedTarget) {
|
|
722
|
+
insertBeforeLine(flowLines, "next_recommended_action:", `next_recommended_target: ${flowSummary.nextRecommendedTarget}`);
|
|
723
|
+
}
|
|
724
|
+
appendConflictSummary(flowLines, summarizeNoConflict({ serverUpdated: flowResult.serverUpdated }));
|
|
725
|
+
appendResponsibilityWarning(flowLines, flowResult.boundaryWarnings);
|
|
726
|
+
appendStructureScore(flowLines, flowResult.structureScore);
|
|
727
|
+
appendMemoryContract(flowLines, flowResult.memoryContract);
|
|
728
|
+
appendUnresolvedWikiLinks(flowLines, flowResult.unresolvedWikiLinks);
|
|
729
|
+
appendDuplicateWarnings(flowLines, flowResult.duplicateWarnings);
|
|
730
|
+
await appendAutoProjectHealth(flowLines);
|
|
731
|
+
return textResult(flowLines.join("\n"));
|
|
732
|
+
}
|
|
733
|
+
// Stock/generic note path
|
|
924
734
|
const result = await store.writeNote({
|
|
925
735
|
title,
|
|
926
736
|
content,
|
|
927
737
|
directory,
|
|
928
738
|
tags,
|
|
929
739
|
noteType: note_type,
|
|
740
|
+
origin,
|
|
741
|
+
createdVia: created_via,
|
|
742
|
+
humanRole: human_role,
|
|
930
743
|
});
|
|
931
744
|
const summary = summarizeWriteNoteResponse({
|
|
932
745
|
operation: result.operation,
|
|
@@ -979,84 +792,6 @@ async function main() {
|
|
|
979
792
|
await appendAutoProjectHealth(lines);
|
|
980
793
|
return textResult(lines.join("\n"));
|
|
981
794
|
});
|
|
982
|
-
server.tool("write_flow_note", "flow ノートを構造テンプレート付きで作成/更新します。", {
|
|
983
|
-
title: z.string().min(1),
|
|
984
|
-
question: z.string().min(1),
|
|
985
|
-
next_action: z.string().min(1),
|
|
986
|
-
parent_stock: z.string().min(1),
|
|
987
|
-
related: z.array(z.string()).optional().describe("追加の関連リンク先"),
|
|
988
|
-
details: z.string().optional().describe("## Notes に入れる本文"),
|
|
989
|
-
directory: z.string().min(1).optional().describe("default: Flow"),
|
|
990
|
-
tags: z.array(z.string()).optional(),
|
|
991
|
-
flow_state: z.string().optional().describe("capture / active / blocked / done / promoted / dropped"),
|
|
992
|
-
}, async ({ title, question, next_action, parent_stock, related, details, directory, tags, flow_state }) => {
|
|
993
|
-
const result = await store.writeFlowNote({
|
|
994
|
-
title,
|
|
995
|
-
question,
|
|
996
|
-
nextAction: next_action,
|
|
997
|
-
parentStock: parent_stock,
|
|
998
|
-
related,
|
|
999
|
-
details,
|
|
1000
|
-
directory,
|
|
1001
|
-
tags,
|
|
1002
|
-
flowState: flow_state,
|
|
1003
|
-
});
|
|
1004
|
-
const summary = summarizeWriteFlowResponse({
|
|
1005
|
-
operation: result.operation,
|
|
1006
|
-
unresolvedWikiLinkCount: result.unresolvedWikiLinks.length,
|
|
1007
|
-
boundaryWarningCount: result.boundaryWarnings.length,
|
|
1008
|
-
memoryContractStatus: result.memoryContract.status,
|
|
1009
|
-
duplicateWarningCount: result.duplicateWarnings.length,
|
|
1010
|
-
orphanWarning: result.orphanWarning,
|
|
1011
|
-
title: result.title,
|
|
1012
|
-
parentStockResolved: result.parentStockResolved,
|
|
1013
|
-
parentStock: result.parentStock,
|
|
1014
|
-
});
|
|
1015
|
-
const lines = [
|
|
1016
|
-
"flow ノートを作成/更新しました。",
|
|
1017
|
-
`project: ${result.project}`,
|
|
1018
|
-
`scope_label: ${TOOL_SCOPE_LABELS.write_flow_note}`,
|
|
1019
|
-
`primary_navigation_signal: ${summary.primaryNavigationSignal}`,
|
|
1020
|
-
`primary_quality_signal: ${summary.primaryQualitySignal}`,
|
|
1021
|
-
`next_recommended_action: ${summary.nextRecommendedAction}`,
|
|
1022
|
-
`operation: ${result.operation}`,
|
|
1023
|
-
`file: ${result.relativePath}`,
|
|
1024
|
-
`permalink: ${result.permalink}`,
|
|
1025
|
-
`title: ${result.title}`,
|
|
1026
|
-
`flow_state: ${result.flowState}`,
|
|
1027
|
-
`parent_stock: ${result.parentStock}`,
|
|
1028
|
-
`parent_stock_resolved: ${result.parentStockResolved ? "yes" : "no"}`,
|
|
1029
|
-
`link_health: ${result.linkHealth.resolved}/${result.linkHealth.total} resolved`,
|
|
1030
|
-
`backlinks: ${result.backlinkCount}`,
|
|
1031
|
-
`orphan_warning: ${result.orphanWarning ? "yes" : "no"}`,
|
|
1032
|
-
];
|
|
1033
|
-
lines.splice(2, 0, ...[
|
|
1034
|
-
`storage_backend: ${config.github ? "github" : "filesystem"}`,
|
|
1035
|
-
`storage_root: ${config.root}`,
|
|
1036
|
-
...(config.github
|
|
1037
|
-
? [`storage_repo: ${config.github.owner}/${config.github.repo}`, `storage_branch: ${config.github.branch}`]
|
|
1038
|
-
: []),
|
|
1039
|
-
]);
|
|
1040
|
-
if (summary.nextRecommendedTarget) {
|
|
1041
|
-
insertBeforeLine(lines, "next_recommended_action:", `next_recommended_target: ${summary.nextRecommendedTarget}`);
|
|
1042
|
-
}
|
|
1043
|
-
appendConflictSummary(lines, summarizeNoConflict({ serverUpdated: result.serverUpdated }));
|
|
1044
|
-
appendResponsibilityWarning(lines, result.boundaryWarnings);
|
|
1045
|
-
appendStructureScore(lines, result.structureScore);
|
|
1046
|
-
appendMemoryContract(lines, result.memoryContract);
|
|
1047
|
-
appendBoundaryWarnings(lines, result.boundaryWarnings);
|
|
1048
|
-
appendTemplateHints(lines, {
|
|
1049
|
-
primary: "flow",
|
|
1050
|
-
alternatives: ["stock", "index"],
|
|
1051
|
-
reason: "進行中の問いと next_action を残すときは flow template を基準にする",
|
|
1052
|
-
whenToSwitch: "知識として固まったら stock、再訪入口を作るなら index template を使う",
|
|
1053
|
-
});
|
|
1054
|
-
appendUnresolvedWikiLinks(lines, result.unresolvedWikiLinks);
|
|
1055
|
-
appendUnresolvedLinkHints(lines, result.unresolvedLinkHints);
|
|
1056
|
-
appendDuplicateWarnings(lines, result.duplicateWarnings);
|
|
1057
|
-
await appendAutoProjectHealth(lines);
|
|
1058
|
-
return textResult(lines.join("\n"));
|
|
1059
|
-
});
|
|
1060
795
|
server.tool("promote_to_stock", "flow ノートを stock ノートへ昇格させ、flow 側の状態を promoted に更新します。", {
|
|
1061
796
|
flow_identifier: z.string().min(1),
|
|
1062
797
|
stock_title: z.string().min(1).optional(),
|
package/dist/src/noteStore.js
CHANGED
|
@@ -2048,6 +2048,9 @@ export class LocalNoteStore {
|
|
|
2048
2048
|
unresolvedLinkHints,
|
|
2049
2049
|
};
|
|
2050
2050
|
}
|
|
2051
|
+
async loadAllNotes() {
|
|
2052
|
+
return this.loadProjectNotes();
|
|
2053
|
+
}
|
|
2051
2054
|
async loadProjectNotes() {
|
|
2052
2055
|
const files = await this.storage.listMarkdownFiles();
|
|
2053
2056
|
return Promise.all(files.map((relativePath) => this.loadNote(relativePath)));
|
|
@@ -2814,6 +2817,143 @@ export class LocalNoteStore {
|
|
|
2814
2817
|
explorationGuidance: this.contextExplorationGuidance(notes, sourceNote, rankedNotes),
|
|
2815
2818
|
};
|
|
2816
2819
|
}
|
|
2820
|
+
async orient(args) {
|
|
2821
|
+
const notes = await this.loadProjectNotes();
|
|
2822
|
+
const lookup = this.buildLookup(notes);
|
|
2823
|
+
const thought = args.thought.trim();
|
|
2824
|
+
// Record observation signal if present
|
|
2825
|
+
if (args.observation && args.signalLog) {
|
|
2826
|
+
const signalType = `human_${args.observation.signal}`;
|
|
2827
|
+
const resolved = this.resolveWikiTarget(args.observation.note, lookup);
|
|
2828
|
+
const noteId = resolved.status === "resolved" && resolved.note
|
|
2829
|
+
? resolved.note.permalink
|
|
2830
|
+
: args.observation.note;
|
|
2831
|
+
args.signalLog.record(signalType, noteId, thought.slice(0, 120));
|
|
2832
|
+
}
|
|
2833
|
+
// Record orient references as signals
|
|
2834
|
+
if (args.references && args.signalLog) {
|
|
2835
|
+
for (const ref of args.references) {
|
|
2836
|
+
const resolved = this.resolveWikiTarget(ref, lookup);
|
|
2837
|
+
const noteId = resolved.status === "resolved" && resolved.note
|
|
2838
|
+
? resolved.note.permalink
|
|
2839
|
+
: ref;
|
|
2840
|
+
args.signalLog.record("orient_referenced", noteId, thought.slice(0, 120));
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
const toOrientItem = (note, reason) => {
|
|
2844
|
+
const engagement = args.engagementIndex?.get(note.permalink);
|
|
2845
|
+
const snippet = note.body.slice(0, this.config.previewLength).trim();
|
|
2846
|
+
return {
|
|
2847
|
+
identifier: note.title,
|
|
2848
|
+
title: note.title,
|
|
2849
|
+
permalink: note.permalink,
|
|
2850
|
+
snippet,
|
|
2851
|
+
reason,
|
|
2852
|
+
engagement: {
|
|
2853
|
+
contactCount: engagement?.contactCount ?? 0,
|
|
2854
|
+
lastContact: engagement?.lastContact ?? null,
|
|
2855
|
+
humanSignalCount: engagement?.humanSignalCount ?? 0,
|
|
2856
|
+
dominantSignal: engagement?.dominantSignal ?? null,
|
|
2857
|
+
aiReferenceCount: engagement?.aiReferenceCount ?? 0,
|
|
2858
|
+
origin: engagement?.origin ?? safeString(note.frontmatter.origin) ?? null,
|
|
2859
|
+
createdVia: engagement?.createdVia ?? safeString(note.frontmatter.created_via) ?? null,
|
|
2860
|
+
},
|
|
2861
|
+
};
|
|
2862
|
+
};
|
|
2863
|
+
// === Slot 1: relevant (search-based + engagement boost) ===
|
|
2864
|
+
const relevant = [];
|
|
2865
|
+
if (thought.length > 0) {
|
|
2866
|
+
for (const note of notes) {
|
|
2867
|
+
let score = this.noteScore(note, thought);
|
|
2868
|
+
if (score <= 0)
|
|
2869
|
+
continue;
|
|
2870
|
+
// Engagement boost
|
|
2871
|
+
const eng = args.engagementIndex?.get(note.permalink);
|
|
2872
|
+
if (eng) {
|
|
2873
|
+
if (eng.humanSignalCount > 0)
|
|
2874
|
+
score = Math.floor(score * 1.5);
|
|
2875
|
+
else if (eng.aiReferenceCount > 0)
|
|
2876
|
+
score = Math.floor(score * 1.2);
|
|
2877
|
+
else if (eng.contactCount === 0)
|
|
2878
|
+
score = Math.floor(score * 0.8);
|
|
2879
|
+
}
|
|
2880
|
+
relevant.push({ note, score, reason: "text_match" });
|
|
2881
|
+
}
|
|
2882
|
+
relevant.sort((a, b) => b.score - a.score);
|
|
2883
|
+
}
|
|
2884
|
+
// Record discovered notes as signals
|
|
2885
|
+
if (args.signalLog) {
|
|
2886
|
+
for (const item of relevant.slice(0, 5)) {
|
|
2887
|
+
args.signalLog.record("orient_discovered", item.note.permalink, thought.slice(0, 120));
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
// === Slot 2: active_context (active flows + recent high-engagement) ===
|
|
2891
|
+
const activeContext = [];
|
|
2892
|
+
const relevantPermalinks = new Set(relevant.slice(0, 5).map((r) => r.note.permalink));
|
|
2893
|
+
for (const note of notes) {
|
|
2894
|
+
if (relevantPermalinks.has(note.permalink))
|
|
2895
|
+
continue;
|
|
2896
|
+
const flowState = safeString(note.frontmatter.flow_state);
|
|
2897
|
+
if (flowState === "active" || flowState === "capture") {
|
|
2898
|
+
activeContext.push({ note, reason: `active_flow (${flowState})` });
|
|
2899
|
+
continue;
|
|
2900
|
+
}
|
|
2901
|
+
const eng = args.engagementIndex?.get(note.permalink);
|
|
2902
|
+
if (eng && eng.contactCount >= 3 && eng.lastContact) {
|
|
2903
|
+
const daysSinceContact = Math.floor((Date.now() - new Date(eng.lastContact).getTime()) / (1000 * 60 * 60 * 24));
|
|
2904
|
+
if (daysSinceContact <= 7) {
|
|
2905
|
+
activeContext.push({ note, reason: `recent_high_engagement (${eng.contactCount} contacts)` });
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
// === Slot 3: discovery (untouched notes with tag/directory adjacency) ===
|
|
2910
|
+
const discovery = [];
|
|
2911
|
+
const usedPermalinks = new Set([
|
|
2912
|
+
...relevantPermalinks,
|
|
2913
|
+
...activeContext.map((a) => a.note.permalink),
|
|
2914
|
+
]);
|
|
2915
|
+
// Extract tags from thought terms for tag adjacency
|
|
2916
|
+
const thoughtTerms = new Set(thought.toLowerCase().split(/[\s\p{P}]+/u).filter((t) => t.length >= 2));
|
|
2917
|
+
for (const note of notes) {
|
|
2918
|
+
if (usedPermalinks.has(note.permalink))
|
|
2919
|
+
continue;
|
|
2920
|
+
const eng = args.engagementIndex?.get(note.permalink);
|
|
2921
|
+
if (eng && eng.contactCount > 0)
|
|
2922
|
+
continue;
|
|
2923
|
+
let discoveryScore = 0;
|
|
2924
|
+
let reasons = [];
|
|
2925
|
+
// Tag adjacency
|
|
2926
|
+
const tags = safeStringArray(note.frontmatter.tags);
|
|
2927
|
+
for (const tag of tags) {
|
|
2928
|
+
if (thoughtTerms.has(tag.toLowerCase())) {
|
|
2929
|
+
discoveryScore += 10;
|
|
2930
|
+
reasons.push(`tag:${tag}`);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
// Weak text match (lower threshold than relevant)
|
|
2934
|
+
if (thought.length > 0) {
|
|
2935
|
+
const textScore = this.noteScore(note, thought);
|
|
2936
|
+
if (textScore > 0) {
|
|
2937
|
+
discoveryScore += Math.min(textScore, 20);
|
|
2938
|
+
reasons.push("weak_text_match");
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
// Random serendipity factor
|
|
2942
|
+
discoveryScore += Math.floor(Math.random() * 5);
|
|
2943
|
+
if (discoveryScore > 0 || Math.random() < 0.02) {
|
|
2944
|
+
if (reasons.length === 0)
|
|
2945
|
+
reasons.push("serendipity");
|
|
2946
|
+
discovery.push({ note, score: discoveryScore, reason: reasons.join(", ") });
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
discovery.sort((a, b) => b.score - a.score);
|
|
2950
|
+
return {
|
|
2951
|
+
project: this.projectName,
|
|
2952
|
+
relevant: relevant.slice(0, 5).map((r) => toOrientItem(r.note, r.reason)),
|
|
2953
|
+
activeContext: activeContext.slice(0, 5).map((a) => toOrientItem(a.note, a.reason)),
|
|
2954
|
+
discovery: discovery.slice(0, 3).map((d) => toOrientItem(d.note, d.reason)),
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2817
2957
|
async writeFlowNote(args) {
|
|
2818
2958
|
await this.storage.ensureRoot();
|
|
2819
2959
|
const flowState = (safeString(args.flowState)?.toLowerCase() ?? "capture");
|
|
@@ -2871,6 +3011,9 @@ export class LocalNoteStore {
|
|
|
2871
3011
|
next_action: nextAction,
|
|
2872
3012
|
parent_stock: parentStock,
|
|
2873
3013
|
},
|
|
3014
|
+
origin: args.origin,
|
|
3015
|
+
createdVia: args.createdVia,
|
|
3016
|
+
humanRole: args.humanRole,
|
|
2874
3017
|
});
|
|
2875
3018
|
const notes = await this.loadProjectNotes();
|
|
2876
3019
|
const lookup = this.buildLookup(notes);
|
|
@@ -3029,6 +3172,15 @@ export class LocalNoteStore {
|
|
|
3029
3172
|
created: safeString(existingFrontmatter.created) ?? date,
|
|
3030
3173
|
updated: date,
|
|
3031
3174
|
};
|
|
3175
|
+
if (input.origin && !existingFrontmatter.origin) {
|
|
3176
|
+
frontmatter.origin = input.origin;
|
|
3177
|
+
}
|
|
3178
|
+
if (input.createdVia && !existingFrontmatter.created_via) {
|
|
3179
|
+
frontmatter.created_via = input.createdVia;
|
|
3180
|
+
}
|
|
3181
|
+
if (input.humanRole && !existingFrontmatter.human_role) {
|
|
3182
|
+
frontmatter.human_role = input.humanRole;
|
|
3183
|
+
}
|
|
3032
3184
|
for (const [key, value] of Object.entries(patch)) {
|
|
3033
3185
|
if (RESERVED_FRONTMATTER_KEYS.has(key)) {
|
|
3034
3186
|
continue;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const SIGNAL_DIR = ".kioq";
|
|
2
|
+
const SIGNAL_FILE = `${SIGNAL_DIR}/signals.jsonl`;
|
|
3
|
+
export class SignalLog {
|
|
4
|
+
storage;
|
|
5
|
+
buffer = [];
|
|
6
|
+
flushPromise = null;
|
|
7
|
+
constructor(storage) {
|
|
8
|
+
this.storage = storage;
|
|
9
|
+
}
|
|
10
|
+
record(op, note, ctx) {
|
|
11
|
+
this.buffer.push({
|
|
12
|
+
ts: new Date().toISOString(),
|
|
13
|
+
op,
|
|
14
|
+
note,
|
|
15
|
+
ctx,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
recordBatch(entries) {
|
|
19
|
+
const ts = new Date().toISOString();
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
this.buffer.push({ ts, op: entry.op, note: entry.note, ctx: entry.ctx });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async flush() {
|
|
25
|
+
if (this.buffer.length === 0)
|
|
26
|
+
return;
|
|
27
|
+
if (this.flushPromise) {
|
|
28
|
+
await this.flushPromise;
|
|
29
|
+
}
|
|
30
|
+
const entries = this.buffer.splice(0);
|
|
31
|
+
const lines = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
32
|
+
this.flushPromise = this.appendToLog(lines);
|
|
33
|
+
try {
|
|
34
|
+
await this.flushPromise;
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
this.flushPromise = null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async load() {
|
|
41
|
+
try {
|
|
42
|
+
const content = await this.storage.readFile(SIGNAL_FILE);
|
|
43
|
+
return content
|
|
44
|
+
.split("\n")
|
|
45
|
+
.filter((line) => line.trim().length > 0)
|
|
46
|
+
.map((line) => JSON.parse(line));
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async appendToLog(content) {
|
|
53
|
+
let existing = "";
|
|
54
|
+
try {
|
|
55
|
+
existing = await this.storage.readFile(SIGNAL_FILE);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// file doesn't exist yet
|
|
59
|
+
}
|
|
60
|
+
await this.storage.writeFile(SIGNAL_FILE, existing + content);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -38,6 +38,8 @@ export class GitHubStorage {
|
|
|
38
38
|
fetchFn;
|
|
39
39
|
execFileAsyncFn;
|
|
40
40
|
setupValidated = false;
|
|
41
|
+
lastFetchMs = 0;
|
|
42
|
+
static FETCH_INTERVAL_MS = 10_000;
|
|
41
43
|
constructor(githubConfig, rootPath, deps = {}) {
|
|
42
44
|
this.githubConfig = githubConfig;
|
|
43
45
|
this.rootPath = rootPath;
|
|
@@ -48,17 +50,21 @@ export class GitHubStorage {
|
|
|
48
50
|
this.fetchFn = deps.fetchFn ?? fetch;
|
|
49
51
|
this.execFileAsyncFn = deps.execFileAsyncFn ?? execFileAsync;
|
|
50
52
|
}
|
|
51
|
-
// --- Reads: delegate to local clone ---
|
|
52
|
-
stat(relativePath) {
|
|
53
|
+
// --- Reads: delegate to local clone (with auto-refresh) ---
|
|
54
|
+
async stat(relativePath) {
|
|
55
|
+
await this.refreshIfStale();
|
|
53
56
|
return this.local.stat(relativePath);
|
|
54
57
|
}
|
|
55
|
-
readFile(relativePath) {
|
|
58
|
+
async readFile(relativePath) {
|
|
59
|
+
await this.refreshIfStale();
|
|
56
60
|
return this.local.readFile(relativePath);
|
|
57
61
|
}
|
|
58
|
-
readdir(relativePath) {
|
|
62
|
+
async readdir(relativePath) {
|
|
63
|
+
await this.refreshIfStale();
|
|
59
64
|
return this.local.readdir(relativePath);
|
|
60
65
|
}
|
|
61
|
-
listMarkdownFiles() {
|
|
66
|
+
async listMarkdownFiles() {
|
|
67
|
+
await this.refreshIfStale();
|
|
62
68
|
return this.local.listMarkdownFiles();
|
|
63
69
|
}
|
|
64
70
|
// --- Writes: GitHub Git Data API ---
|
|
@@ -352,6 +358,7 @@ export class GitHubStorage {
|
|
|
352
358
|
cwd: this.rootPath,
|
|
353
359
|
timeout: 30_000,
|
|
354
360
|
});
|
|
361
|
+
this.lastFetchMs = Date.now();
|
|
355
362
|
}
|
|
356
363
|
catch (error) {
|
|
357
364
|
throw new Error(`${context}: ${this.normalizeExecError(error)}`);
|
|
@@ -365,6 +372,25 @@ export class GitHubStorage {
|
|
|
365
372
|
// best-effort only
|
|
366
373
|
}
|
|
367
374
|
}
|
|
375
|
+
async refreshIfStale() {
|
|
376
|
+
const now = Date.now();
|
|
377
|
+
if (now - this.lastFetchMs < GitHubStorage.FETCH_INTERVAL_MS) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const { stdout: localRef } = await this.execFileAsyncFn("git", ["rev-parse", `refs/heads/${this.branch}`], { cwd: this.rootPath, timeout: 5_000 });
|
|
382
|
+
await this.execFileAsyncFn("git", ["fetch", "--no-tags", "origin", this.branch], { cwd: this.rootPath, timeout: 15_000 });
|
|
383
|
+
const { stdout: remoteRef } = await this.execFileAsyncFn("git", ["rev-parse", `refs/remotes/origin/${this.branch}`], { cwd: this.rootPath, timeout: 5_000 });
|
|
384
|
+
this.lastFetchMs = Date.now();
|
|
385
|
+
if (localRef.trim() !== remoteRef.trim()) {
|
|
386
|
+
await this.pullLocal("git pull --ff-only failed during auto-refresh");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
// best-effort: if fetch fails, proceed with stale data
|
|
391
|
+
this.lastFetchMs = Date.now();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
368
394
|
isRetryableBatchError(error) {
|
|
369
395
|
return (error instanceof GitHubApiError && error.retryable)
|
|
370
396
|
|| this.isTransientNetworkError(error);
|