@velvetmonkey/flywheel-memory 2.0.6 → 2.0.8
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/index.js +73 -65
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -2732,9 +2732,9 @@ import { simpleGit, CheckRepoActions } from "simple-git";
|
|
|
2732
2732
|
import path5 from "path";
|
|
2733
2733
|
import fs5 from "fs/promises";
|
|
2734
2734
|
import {
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2735
|
+
setWriteState,
|
|
2736
|
+
getWriteState,
|
|
2737
|
+
deleteWriteState
|
|
2738
2738
|
} from "@velvetmonkey/vault-core";
|
|
2739
2739
|
var moduleStateDb = null;
|
|
2740
2740
|
function setGitStateDb(stateDb2) {
|
|
@@ -2747,9 +2747,9 @@ var DEFAULT_RETRY = {
|
|
|
2747
2747
|
jitter: true
|
|
2748
2748
|
};
|
|
2749
2749
|
var STALE_LOCK_THRESHOLD_MS = 3e4;
|
|
2750
|
-
function
|
|
2750
|
+
function saveLastMutationCommit(hash, message) {
|
|
2751
2751
|
if (!moduleStateDb) {
|
|
2752
|
-
console.error("[
|
|
2752
|
+
console.error("[Flywheel] No StateDb available for saving last commit");
|
|
2753
2753
|
return;
|
|
2754
2754
|
}
|
|
2755
2755
|
const data = {
|
|
@@ -2758,27 +2758,27 @@ function saveLastCrankCommit(hash, message) {
|
|
|
2758
2758
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2759
2759
|
};
|
|
2760
2760
|
try {
|
|
2761
|
-
|
|
2761
|
+
setWriteState(moduleStateDb, "last_commit", data);
|
|
2762
2762
|
} catch (e) {
|
|
2763
|
-
console.error("[
|
|
2763
|
+
console.error("[Flywheel] Failed to save last commit to StateDb:", e);
|
|
2764
2764
|
}
|
|
2765
2765
|
}
|
|
2766
|
-
function
|
|
2766
|
+
function getLastMutationCommit() {
|
|
2767
2767
|
if (!moduleStateDb) {
|
|
2768
2768
|
return null;
|
|
2769
2769
|
}
|
|
2770
2770
|
try {
|
|
2771
|
-
return
|
|
2771
|
+
return getWriteState(moduleStateDb, "last_commit");
|
|
2772
2772
|
} catch {
|
|
2773
2773
|
return null;
|
|
2774
2774
|
}
|
|
2775
2775
|
}
|
|
2776
|
-
function
|
|
2776
|
+
function clearLastMutationCommit() {
|
|
2777
2777
|
if (!moduleStateDb) {
|
|
2778
2778
|
return;
|
|
2779
2779
|
}
|
|
2780
2780
|
try {
|
|
2781
|
-
|
|
2781
|
+
deleteWriteState(moduleStateDb, "last_commit");
|
|
2782
2782
|
} catch {
|
|
2783
2783
|
}
|
|
2784
2784
|
}
|
|
@@ -2860,7 +2860,7 @@ async function commitChange(vaultPath2, filePath, messagePrefix, retryConfig = D
|
|
|
2860
2860
|
const commitMessage = `${messagePrefix} Update ${fileName}`;
|
|
2861
2861
|
const result = await git.commit(commitMessage);
|
|
2862
2862
|
if (result.commit) {
|
|
2863
|
-
|
|
2863
|
+
saveLastMutationCommit(result.commit, commitMessage);
|
|
2864
2864
|
}
|
|
2865
2865
|
return {
|
|
2866
2866
|
success: true,
|
|
@@ -2995,7 +2995,7 @@ Files:
|
|
|
2995
2995
|
${files.map((f) => `- ${f}`).join("\n")}`;
|
|
2996
2996
|
const result = await git.commit(commitMessage);
|
|
2997
2997
|
if (result.commit) {
|
|
2998
|
-
|
|
2998
|
+
saveLastMutationCommit(result.commit, commitMessage);
|
|
2999
2999
|
}
|
|
3000
3000
|
return {
|
|
3001
3001
|
success: true,
|
|
@@ -3040,8 +3040,8 @@ ${files.map((f) => `- ${f}`).join("\n")}`;
|
|
|
3040
3040
|
|
|
3041
3041
|
// src/core/write/hints.ts
|
|
3042
3042
|
import {
|
|
3043
|
-
|
|
3044
|
-
|
|
3043
|
+
setWriteState as setWriteState2,
|
|
3044
|
+
getWriteState as getWriteState2
|
|
3045
3045
|
} from "@velvetmonkey/vault-core";
|
|
3046
3046
|
var moduleStateDb2 = null;
|
|
3047
3047
|
function setHintsStateDb(stateDb2) {
|
|
@@ -3115,7 +3115,7 @@ async function buildRecencyIndex(vaultPath2, entities) {
|
|
|
3115
3115
|
}
|
|
3116
3116
|
}
|
|
3117
3117
|
} catch (error) {
|
|
3118
|
-
console.error(`[
|
|
3118
|
+
console.error(`[Flywheel] Error building recency index: ${error}`);
|
|
3119
3119
|
}
|
|
3120
3120
|
return {
|
|
3121
3121
|
lastMentioned,
|
|
@@ -3158,16 +3158,16 @@ function loadRecencyFromStateDb() {
|
|
|
3158
3158
|
}
|
|
3159
3159
|
function saveRecencyToStateDb(index) {
|
|
3160
3160
|
if (!moduleStateDb3) {
|
|
3161
|
-
console.error("[
|
|
3161
|
+
console.error("[Flywheel] No StateDb available for saving recency");
|
|
3162
3162
|
return;
|
|
3163
3163
|
}
|
|
3164
3164
|
try {
|
|
3165
3165
|
for (const [entityNameLower, timestamp] of index.lastMentioned) {
|
|
3166
3166
|
recordEntityMention(moduleStateDb3, entityNameLower, new Date(timestamp));
|
|
3167
3167
|
}
|
|
3168
|
-
console.error(`[
|
|
3168
|
+
console.error(`[Flywheel] Saved ${index.lastMentioned.size} recency entries to StateDb`);
|
|
3169
3169
|
} catch (e) {
|
|
3170
|
-
console.error("[
|
|
3170
|
+
console.error("[Flywheel] Failed to save recency to StateDb:", e);
|
|
3171
3171
|
}
|
|
3172
3172
|
}
|
|
3173
3173
|
|
|
@@ -4068,7 +4068,7 @@ function getCooccurrenceBoost(entityName, matchedEntities, cooccurrenceIndex2, r
|
|
|
4068
4068
|
|
|
4069
4069
|
// src/core/write/wikilinks.ts
|
|
4070
4070
|
var moduleStateDb4 = null;
|
|
4071
|
-
function
|
|
4071
|
+
function setWriteStateDb(stateDb2) {
|
|
4072
4072
|
moduleStateDb4 = stateDb2;
|
|
4073
4073
|
setGitStateDb(stateDb2);
|
|
4074
4074
|
setHintsStateDb(stateDb2);
|
|
@@ -4113,21 +4113,21 @@ async function initializeEntityIndex(vaultPath2) {
|
|
|
4113
4113
|
entityIndex = dbIndex;
|
|
4114
4114
|
indexReady = true;
|
|
4115
4115
|
lastLoadedAt = Date.now();
|
|
4116
|
-
console.error(`[
|
|
4116
|
+
console.error(`[Flywheel] Loaded ${dbIndex._metadata.total_entities} entities from StateDb`);
|
|
4117
4117
|
return;
|
|
4118
4118
|
}
|
|
4119
4119
|
} catch (e) {
|
|
4120
|
-
console.error("[
|
|
4120
|
+
console.error("[Flywheel] Failed to load from StateDb:", e);
|
|
4121
4121
|
}
|
|
4122
4122
|
}
|
|
4123
4123
|
await rebuildIndex(vaultPath2);
|
|
4124
4124
|
} catch (error) {
|
|
4125
4125
|
indexError2 = error instanceof Error ? error : new Error(String(error));
|
|
4126
|
-
console.error(`[
|
|
4126
|
+
console.error(`[Flywheel] Failed to initialize entity index: ${indexError2.message}`);
|
|
4127
4127
|
}
|
|
4128
4128
|
}
|
|
4129
4129
|
async function rebuildIndex(vaultPath2) {
|
|
4130
|
-
console.error(`[
|
|
4130
|
+
console.error(`[Flywheel] Scanning vault for entities...`);
|
|
4131
4131
|
const startTime = Date.now();
|
|
4132
4132
|
entityIndex = await scanVaultEntities(vaultPath2, {
|
|
4133
4133
|
excludeFolders: DEFAULT_EXCLUDE_FOLDERS
|
|
@@ -4135,13 +4135,13 @@ async function rebuildIndex(vaultPath2) {
|
|
|
4135
4135
|
indexReady = true;
|
|
4136
4136
|
lastLoadedAt = Date.now();
|
|
4137
4137
|
const entityDuration = Date.now() - startTime;
|
|
4138
|
-
console.error(`[
|
|
4138
|
+
console.error(`[Flywheel] Entity index built: ${entityIndex._metadata.total_entities} entities in ${entityDuration}ms`);
|
|
4139
4139
|
if (moduleStateDb4) {
|
|
4140
4140
|
try {
|
|
4141
4141
|
moduleStateDb4.replaceAllEntities(entityIndex);
|
|
4142
|
-
console.error(`[
|
|
4142
|
+
console.error(`[Flywheel] Saved entities to StateDb`);
|
|
4143
4143
|
} catch (e) {
|
|
4144
|
-
console.error(`[
|
|
4144
|
+
console.error(`[Flywheel] Failed to save entities to StateDb: ${e}`);
|
|
4145
4145
|
}
|
|
4146
4146
|
}
|
|
4147
4147
|
const entities = getAllEntities(entityIndex);
|
|
@@ -4150,25 +4150,25 @@ async function rebuildIndex(vaultPath2) {
|
|
|
4150
4150
|
const cooccurrenceStart = Date.now();
|
|
4151
4151
|
cooccurrenceIndex = await mineCooccurrences(vaultPath2, entityNames);
|
|
4152
4152
|
const cooccurrenceDuration = Date.now() - cooccurrenceStart;
|
|
4153
|
-
console.error(`[
|
|
4153
|
+
console.error(`[Flywheel] Co-occurrence index built: ${cooccurrenceIndex._metadata.total_associations} associations in ${cooccurrenceDuration}ms`);
|
|
4154
4154
|
} catch (e) {
|
|
4155
|
-
console.error(`[
|
|
4155
|
+
console.error(`[Flywheel] Failed to build co-occurrence index: ${e}`);
|
|
4156
4156
|
}
|
|
4157
4157
|
try {
|
|
4158
4158
|
const cachedRecency = loadRecencyFromStateDb();
|
|
4159
4159
|
const cacheAgeMs = cachedRecency ? Date.now() - cachedRecency.lastUpdated : Infinity;
|
|
4160
4160
|
if (cachedRecency && cacheAgeMs < 60 * 60 * 1e3) {
|
|
4161
4161
|
recencyIndex = cachedRecency;
|
|
4162
|
-
console.error(`[
|
|
4162
|
+
console.error(`[Flywheel] Recency index loaded from StateDb (${recencyIndex.lastMentioned.size} entities)`);
|
|
4163
4163
|
} else {
|
|
4164
4164
|
const recencyStart = Date.now();
|
|
4165
4165
|
recencyIndex = await buildRecencyIndex(vaultPath2, entities);
|
|
4166
4166
|
const recencyDuration = Date.now() - recencyStart;
|
|
4167
|
-
console.error(`[
|
|
4167
|
+
console.error(`[Flywheel] Recency index built: ${recencyIndex.lastMentioned.size} entities in ${recencyDuration}ms`);
|
|
4168
4168
|
saveRecencyToStateDb(recencyIndex);
|
|
4169
4169
|
}
|
|
4170
4170
|
} catch (e) {
|
|
4171
|
-
console.error(`[
|
|
4171
|
+
console.error(`[Flywheel] Failed to build recency index: ${e}`);
|
|
4172
4172
|
}
|
|
4173
4173
|
}
|
|
4174
4174
|
function isEntityIndexReady() {
|
|
@@ -4181,16 +4181,16 @@ function checkAndRefreshIfStale() {
|
|
|
4181
4181
|
if (!metadata.entitiesBuiltAt) return;
|
|
4182
4182
|
const dbBuiltAt = new Date(metadata.entitiesBuiltAt).getTime();
|
|
4183
4183
|
if (dbBuiltAt > lastLoadedAt) {
|
|
4184
|
-
console.error("[
|
|
4184
|
+
console.error("[Flywheel] Entity index stale, reloading from StateDb...");
|
|
4185
4185
|
const dbIndex = getEntityIndexFromDb(moduleStateDb4);
|
|
4186
4186
|
if (dbIndex._metadata.total_entities > 0) {
|
|
4187
4187
|
entityIndex = dbIndex;
|
|
4188
4188
|
lastLoadedAt = Date.now();
|
|
4189
|
-
console.error(`[
|
|
4189
|
+
console.error(`[Flywheel] Reloaded ${dbIndex._metadata.total_entities} entities`);
|
|
4190
4190
|
}
|
|
4191
4191
|
}
|
|
4192
4192
|
} catch (e) {
|
|
4193
|
-
console.error("[
|
|
4193
|
+
console.error("[Flywheel] Failed to check for stale entities:", e);
|
|
4194
4194
|
}
|
|
4195
4195
|
}
|
|
4196
4196
|
function sortEntitiesByPriority(entities, notePath) {
|
|
@@ -4210,7 +4210,7 @@ function sortEntitiesByPriority(entities, notePath) {
|
|
|
4210
4210
|
}
|
|
4211
4211
|
function processWikilinks(content, notePath) {
|
|
4212
4212
|
if (!isEntityIndexReady() || !entityIndex) {
|
|
4213
|
-
console.error("[
|
|
4213
|
+
console.error("[Flywheel:DEBUG] Entity index not ready, entities:", entityIndex?._metadata?.total_entities ?? 0);
|
|
4214
4214
|
return {
|
|
4215
4215
|
content,
|
|
4216
4216
|
linksAdded: 0,
|
|
@@ -4218,7 +4218,7 @@ function processWikilinks(content, notePath) {
|
|
|
4218
4218
|
};
|
|
4219
4219
|
}
|
|
4220
4220
|
const entities = getAllEntities(entityIndex);
|
|
4221
|
-
console.error(`[
|
|
4221
|
+
console.error(`[Flywheel:DEBUG] Processing wikilinks with ${entities.length} entities`);
|
|
4222
4222
|
const sortedEntities = sortEntitiesByPriority(entities, notePath);
|
|
4223
4223
|
const resolved = resolveAliasWikilinks(content, sortedEntities, {
|
|
4224
4224
|
caseInsensitive: true
|
|
@@ -4818,9 +4818,9 @@ async function initializeLogger2(vaultPath2) {
|
|
|
4818
4818
|
try {
|
|
4819
4819
|
const sessionId = generateSessionId2();
|
|
4820
4820
|
setSessionId2(sessionId);
|
|
4821
|
-
logger2 = await createLoggerFromConfig2(vaultPath2, "
|
|
4821
|
+
logger2 = await createLoggerFromConfig2(vaultPath2, "write");
|
|
4822
4822
|
} catch (error) {
|
|
4823
|
-
console.error(`[
|
|
4823
|
+
console.error(`[Flywheel] Failed to initialize logger: ${error}`);
|
|
4824
4824
|
logger2 = null;
|
|
4825
4825
|
}
|
|
4826
4826
|
}
|
|
@@ -9069,7 +9069,7 @@ function registerMutationTools(server2, vaultPath2, getConfig = () => ({})) {
|
|
|
9069
9069
|
vaultPath: vaultPath2,
|
|
9070
9070
|
notePath,
|
|
9071
9071
|
commit,
|
|
9072
|
-
commitPrefix: "[
|
|
9072
|
+
commitPrefix: "[Flywheel:Add]",
|
|
9073
9073
|
section,
|
|
9074
9074
|
actionDescription: "add content",
|
|
9075
9075
|
scoping: agent_id || session_id ? { agent_id, session_id } : void 0
|
|
@@ -9144,7 +9144,7 @@ function registerMutationTools(server2, vaultPath2, getConfig = () => ({})) {
|
|
|
9144
9144
|
vaultPath: vaultPath2,
|
|
9145
9145
|
notePath,
|
|
9146
9146
|
commit,
|
|
9147
|
-
commitPrefix: "[
|
|
9147
|
+
commitPrefix: "[Flywheel:Remove]",
|
|
9148
9148
|
section,
|
|
9149
9149
|
actionDescription: "remove content",
|
|
9150
9150
|
scoping: agent_id || session_id ? { agent_id, session_id } : void 0
|
|
@@ -9195,7 +9195,7 @@ function registerMutationTools(server2, vaultPath2, getConfig = () => ({})) {
|
|
|
9195
9195
|
vaultPath: vaultPath2,
|
|
9196
9196
|
notePath,
|
|
9197
9197
|
commit,
|
|
9198
|
-
commitPrefix: "[
|
|
9198
|
+
commitPrefix: "[Flywheel:Replace]",
|
|
9199
9199
|
section,
|
|
9200
9200
|
actionDescription: "replace content",
|
|
9201
9201
|
scoping: agent_id || session_id ? { agent_id, session_id } : void 0
|
|
@@ -9338,7 +9338,7 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
9338
9338
|
finalFrontmatter = injectMutationMetadata(frontmatter, { agent_id, session_id });
|
|
9339
9339
|
}
|
|
9340
9340
|
await writeVaultFile(vaultPath2, notePath, toggleResult.content, finalFrontmatter);
|
|
9341
|
-
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[
|
|
9341
|
+
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Task]");
|
|
9342
9342
|
const newStatus = toggleResult.newState ? "completed" : "incomplete";
|
|
9343
9343
|
const checkbox = toggleResult.newState ? "[x]" : "[ ]";
|
|
9344
9344
|
return formatMcpResult(
|
|
@@ -9379,7 +9379,7 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
9379
9379
|
vaultPath: vaultPath2,
|
|
9380
9380
|
notePath,
|
|
9381
9381
|
commit,
|
|
9382
|
-
commitPrefix: "[
|
|
9382
|
+
commitPrefix: "[Flywheel:Task]",
|
|
9383
9383
|
section,
|
|
9384
9384
|
actionDescription: "add task",
|
|
9385
9385
|
scoping: agent_id || session_id ? { agent_id, session_id } : void 0
|
|
@@ -9446,7 +9446,7 @@ function registerFrontmatterTools(server2, vaultPath2) {
|
|
|
9446
9446
|
vaultPath: vaultPath2,
|
|
9447
9447
|
notePath,
|
|
9448
9448
|
commit,
|
|
9449
|
-
commitPrefix: "[
|
|
9449
|
+
commitPrefix: "[Flywheel:FM]",
|
|
9450
9450
|
actionDescription: "update frontmatter"
|
|
9451
9451
|
},
|
|
9452
9452
|
async (ctx) => {
|
|
@@ -9559,7 +9559,7 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
9559
9559
|
finalFrontmatter = injectMutationMetadata(frontmatter, { agent_id, session_id });
|
|
9560
9560
|
}
|
|
9561
9561
|
await writeVaultFile(vaultPath2, notePath, processedContent, finalFrontmatter);
|
|
9562
|
-
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[
|
|
9562
|
+
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Create]");
|
|
9563
9563
|
const infoLines = [wikilinkInfo, suggestInfo].filter(Boolean);
|
|
9564
9564
|
const previewLines = [
|
|
9565
9565
|
`Frontmatter fields: ${Object.keys(frontmatter).join(", ") || "none"}`,
|
|
@@ -9639,7 +9639,7 @@ ${sources}`;
|
|
|
9639
9639
|
}
|
|
9640
9640
|
const fullPath = path18.join(vaultPath2, notePath);
|
|
9641
9641
|
await fs18.unlink(fullPath);
|
|
9642
|
-
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[
|
|
9642
|
+
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Delete]");
|
|
9643
9643
|
const message = backlinkWarning ? `Deleted note: ${notePath}
|
|
9644
9644
|
|
|
9645
9645
|
Warning: ${backlinkWarning}` : `Deleted note: ${notePath}`;
|
|
@@ -9843,7 +9843,7 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
9843
9843
|
let lockAgeMs;
|
|
9844
9844
|
if (commit) {
|
|
9845
9845
|
const filesToCommit = [newPath, ...backlinkUpdates.map((b) => b.path)];
|
|
9846
|
-
const gitResult = await commitChange(vaultPath2, filesToCommit.join(", "), `[
|
|
9846
|
+
const gitResult = await commitChange(vaultPath2, filesToCommit.join(", "), `[Flywheel:Move] ${oldPath} \u2192 ${newPath}`);
|
|
9847
9847
|
if (gitResult.success && gitResult.hash) {
|
|
9848
9848
|
gitCommit = gitResult.hash;
|
|
9849
9849
|
undoAvailable = gitResult.undoAvailable;
|
|
@@ -9854,7 +9854,7 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
9854
9854
|
}
|
|
9855
9855
|
}
|
|
9856
9856
|
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
9857
|
-
console.error(`[
|
|
9857
|
+
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
9858
9858
|
});
|
|
9859
9859
|
const previewLines = [
|
|
9860
9860
|
`Moved: ${oldPath} \u2192 ${newPath}`
|
|
@@ -9918,7 +9918,7 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
9918
9918
|
}
|
|
9919
9919
|
const sanitizedTitle = newTitle.replace(/[<>:"/\\|?*]/g, "");
|
|
9920
9920
|
if (sanitizedTitle !== newTitle) {
|
|
9921
|
-
console.error(`[
|
|
9921
|
+
console.error(`[Flywheel] Title sanitized: "${newTitle}" \u2192 "${sanitizedTitle}"`);
|
|
9922
9922
|
}
|
|
9923
9923
|
const fullPath = path19.join(vaultPath2, notePath);
|
|
9924
9924
|
const dir = path19.dirname(notePath);
|
|
@@ -9983,7 +9983,7 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
9983
9983
|
let lockAgeMs;
|
|
9984
9984
|
if (commit) {
|
|
9985
9985
|
const filesToCommit = [newPath, ...backlinkUpdates.map((b) => b.path)];
|
|
9986
|
-
const gitResult = await commitChange(vaultPath2, filesToCommit.join(", "), `[
|
|
9986
|
+
const gitResult = await commitChange(vaultPath2, filesToCommit.join(", "), `[Flywheel:Rename] ${oldTitle} \u2192 ${sanitizedTitle}`);
|
|
9987
9987
|
if (gitResult.success && gitResult.hash) {
|
|
9988
9988
|
gitCommit = gitResult.hash;
|
|
9989
9989
|
undoAvailable = gitResult.undoAvailable;
|
|
@@ -9994,7 +9994,7 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
9994
9994
|
}
|
|
9995
9995
|
}
|
|
9996
9996
|
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
9997
|
-
console.error(`[
|
|
9997
|
+
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
9998
9998
|
});
|
|
9999
9999
|
const previewLines = [
|
|
10000
10000
|
`Renamed: "${oldTitle}" \u2192 "${sanitizedTitle}"`
|
|
@@ -10036,7 +10036,7 @@ import { z as z16 } from "zod";
|
|
|
10036
10036
|
function registerSystemTools2(server2, vaultPath2) {
|
|
10037
10037
|
server2.tool(
|
|
10038
10038
|
"vault_undo_last_mutation",
|
|
10039
|
-
"Undo the last git commit (typically the last
|
|
10039
|
+
"Undo the last git commit (typically the last Flywheel mutation). Performs a soft reset.",
|
|
10040
10040
|
{
|
|
10041
10041
|
confirm: z16.boolean().default(false).describe("Must be true to confirm undo operation"),
|
|
10042
10042
|
hash: z16.string().optional().describe("Expected commit hash. If provided, undo only proceeds if HEAD matches this hash. Prevents accidentally undoing the wrong commit.")
|
|
@@ -10095,15 +10095,15 @@ Actual HEAD: ${currentHead.hash} "${currentHead.message}"`
|
|
|
10095
10095
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
10096
10096
|
}
|
|
10097
10097
|
}
|
|
10098
|
-
const
|
|
10098
|
+
const lastMutationCommit = getLastMutationCommit();
|
|
10099
10099
|
const lastCommit = await getLastCommit(vaultPath2);
|
|
10100
|
-
if (
|
|
10101
|
-
if (lastCommit.hash !==
|
|
10100
|
+
if (lastMutationCommit && lastCommit) {
|
|
10101
|
+
if (lastCommit.hash !== lastMutationCommit.hash) {
|
|
10102
10102
|
const result2 = {
|
|
10103
10103
|
success: false,
|
|
10104
|
-
message: `Cannot undo: HEAD (${lastCommit.hash.substring(0, 7)}) doesn't match last
|
|
10104
|
+
message: `Cannot undo: HEAD (${lastCommit.hash.substring(0, 7)}) doesn't match last Flywheel commit (${lastMutationCommit.hash.substring(0, 7)}). Another process may have committed since your mutation.`,
|
|
10105
10105
|
path: "",
|
|
10106
|
-
preview: `Expected: ${
|
|
10106
|
+
preview: `Expected: ${lastMutationCommit.hash.substring(0, 7)} "${lastMutationCommit.message}"
|
|
10107
10107
|
Actual HEAD: ${lastCommit.hash.substring(0, 7)} "${lastCommit.message}"`
|
|
10108
10108
|
};
|
|
10109
10109
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
@@ -10118,7 +10118,7 @@ Actual HEAD: ${lastCommit.hash.substring(0, 7)} "${lastCommit.message}"`
|
|
|
10118
10118
|
};
|
|
10119
10119
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
10120
10120
|
}
|
|
10121
|
-
|
|
10121
|
+
clearLastMutationCommit();
|
|
10122
10122
|
const result = {
|
|
10123
10123
|
success: true,
|
|
10124
10124
|
message: undoResult.message,
|
|
@@ -11477,9 +11477,8 @@ var vaultIndex;
|
|
|
11477
11477
|
var flywheelConfig = {};
|
|
11478
11478
|
var stateDb = null;
|
|
11479
11479
|
var PRESETS = {
|
|
11480
|
-
//
|
|
11481
|
-
minimal: ["search", "
|
|
11482
|
-
// All tools (default)
|
|
11480
|
+
// Presets
|
|
11481
|
+
minimal: ["search", "structure", "append", "frontmatter", "notes"],
|
|
11483
11482
|
full: [
|
|
11484
11483
|
"search",
|
|
11485
11484
|
"backlinks",
|
|
@@ -11496,7 +11495,13 @@ var PRESETS = {
|
|
|
11496
11495
|
"notes",
|
|
11497
11496
|
"git",
|
|
11498
11497
|
"policy"
|
|
11499
|
-
]
|
|
11498
|
+
],
|
|
11499
|
+
// Composable bundles
|
|
11500
|
+
graph: ["backlinks", "orphans", "hubs", "paths"],
|
|
11501
|
+
analysis: ["schema", "wikilinks"],
|
|
11502
|
+
tasks: ["tasks"],
|
|
11503
|
+
health: ["health"],
|
|
11504
|
+
ops: ["git", "policy"]
|
|
11500
11505
|
};
|
|
11501
11506
|
var ALL_CATEGORIES = [
|
|
11502
11507
|
"backlinks",
|
|
@@ -11552,7 +11557,6 @@ var TOOL_CATEGORY = {
|
|
|
11552
11557
|
get_folder_structure: "health",
|
|
11553
11558
|
refresh_index: "health",
|
|
11554
11559
|
// absorbed rebuild_search_index
|
|
11555
|
-
get_note_metadata: "health",
|
|
11556
11560
|
get_all_entities: "health",
|
|
11557
11561
|
get_unlinked_mentions: "health",
|
|
11558
11562
|
// search (unified: metadata + content + entities)
|
|
@@ -11573,6 +11577,7 @@ var TOOL_CATEGORY = {
|
|
|
11573
11577
|
get_note_structure: "structure",
|
|
11574
11578
|
get_section_content: "structure",
|
|
11575
11579
|
find_sections: "structure",
|
|
11580
|
+
get_note_metadata: "structure",
|
|
11576
11581
|
// tasks (unified: all task queries + write)
|
|
11577
11582
|
tasks: "tasks",
|
|
11578
11583
|
vault_toggle_task: "tasks",
|
|
@@ -11665,7 +11670,7 @@ async function main() {
|
|
|
11665
11670
|
stateDb = openStateDb(vaultPath);
|
|
11666
11671
|
console.error("[Memory] StateDb initialized");
|
|
11667
11672
|
setFTS5Database(stateDb.db);
|
|
11668
|
-
|
|
11673
|
+
setWriteStateDb(stateDb);
|
|
11669
11674
|
await initializeEntityIndex(vaultPath);
|
|
11670
11675
|
} catch (err) {
|
|
11671
11676
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -11812,6 +11817,9 @@ async function runPostIndexWork(index) {
|
|
|
11812
11817
|
} else {
|
|
11813
11818
|
const debounceMs = parseInt(process.env.FLYWHEEL_DEBOUNCE_MS || "60000");
|
|
11814
11819
|
console.error(`[Memory] File watcher v1 enabled (debounce: ${debounceMs}ms)`);
|
|
11820
|
+
if (debounceMs >= 6e4) {
|
|
11821
|
+
console.error("[Memory] Warning: Legacy watcher using high debounce (60s). Set FLYWHEEL_WATCH_V2=true for 200ms responsiveness.");
|
|
11822
|
+
}
|
|
11815
11823
|
const legacyWatcher = chokidar2.watch(vaultPath, {
|
|
11816
11824
|
ignored: /(^|[\/\\])\../,
|
|
11817
11825
|
persistent: true,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "MCP server that gives Claude full read/write access to your Obsidian vault.
|
|
3
|
+
"version": "2.0.8",
|
|
4
|
+
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. 36 tools for search, backlinks, graph queries, and mutations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"test:watch": "vitest",
|
|
41
41
|
"test:read": "vitest run test/read/",
|
|
42
42
|
"test:write": "vitest run test/write/",
|
|
43
|
+
"test:security": "vitest run test/write/security/",
|
|
44
|
+
"test:stress": "vitest run test/write/stress/ test/write/battle-hardening/",
|
|
43
45
|
"test:coverage": "vitest run --coverage",
|
|
44
46
|
"test:ci": "vitest run --reporter=github-actions",
|
|
45
47
|
"lint": "tsc --noEmit",
|
|
@@ -48,7 +50,7 @@
|
|
|
48
50
|
},
|
|
49
51
|
"dependencies": {
|
|
50
52
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
51
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
53
|
+
"@velvetmonkey/vault-core": "^2.0.8",
|
|
52
54
|
"better-sqlite3": "^11.0.0",
|
|
53
55
|
"chokidar": "^4.0.0",
|
|
54
56
|
"gray-matter": "^4.0.3",
|