memory-bank-skill 7.2.0 → 7.2.2
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/cli.js +29 -1
- package/dist/plugin.js +112 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ import { fileURLToPath } from "url";
|
|
|
27
27
|
// package.json
|
|
28
28
|
var package_default = {
|
|
29
29
|
name: "memory-bank-skill",
|
|
30
|
-
version: "7.2.
|
|
30
|
+
version: "7.2.2",
|
|
31
31
|
description: "Memory Bank - \u9879\u76EE\u8BB0\u5FC6\u7CFB\u7EDF\uFF0C\u8BA9 AI \u52A9\u624B\u5728\u6BCF\u6B21\u5BF9\u8BDD\u4E2D\u90FD\u80FD\u5FEB\u901F\u7406\u89E3\u9879\u76EE\u4E0A\u4E0B\u6587",
|
|
32
32
|
type: "module",
|
|
33
33
|
main: "dist/plugin.js",
|
|
@@ -628,6 +628,28 @@ async function checkAndCleanOpenCodeCache() {
|
|
|
628
628
|
}
|
|
629
629
|
return { cleaned: false };
|
|
630
630
|
}
|
|
631
|
+
async function cleanupLegacySkillDir() {
|
|
632
|
+
const legacyDir = join(homedir(), ".config", "opencode", "skill", "memory-bank");
|
|
633
|
+
if (!await exists(legacyDir)) {
|
|
634
|
+
return { cleaned: false };
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
await fs.rm(legacyDir, { recursive: true, force: true });
|
|
638
|
+
const parentDir = join(homedir(), ".config", "opencode", "skill");
|
|
639
|
+
try {
|
|
640
|
+
const remaining = await fs.readdir(parentDir);
|
|
641
|
+
if (remaining.length === 0) {
|
|
642
|
+
await fs.rmdir(parentDir);
|
|
643
|
+
}
|
|
644
|
+
} catch {}
|
|
645
|
+
return {
|
|
646
|
+
cleaned: true,
|
|
647
|
+
message: `Removed legacy skill/ directory (was: ${legacyDir})`
|
|
648
|
+
};
|
|
649
|
+
} catch {
|
|
650
|
+
return { cleaned: false };
|
|
651
|
+
}
|
|
652
|
+
}
|
|
631
653
|
async function install(customModel) {
|
|
632
654
|
log(`
|
|
633
655
|
${colors.bold}Memory Bank Skill Installer v${VERSION}${colors.reset}
|
|
@@ -640,6 +662,12 @@ ${colors.bold}Memory Bank Skill Installer v${VERSION}${colors.reset}
|
|
|
640
662
|
const cacheResult = await checkAndCleanOpenCodeCache();
|
|
641
663
|
if (cacheResult.cleaned) {
|
|
642
664
|
logSuccess(cacheResult.message || "Cleaned OpenCode cache");
|
|
665
|
+
}
|
|
666
|
+
const legacyResult = await cleanupLegacySkillDir();
|
|
667
|
+
if (legacyResult.cleaned) {
|
|
668
|
+
logSuccess(legacyResult.message || "Cleaned legacy skill/ directory");
|
|
669
|
+
}
|
|
670
|
+
if (cacheResult.cleaned || legacyResult.cleaned) {
|
|
643
671
|
log("");
|
|
644
672
|
}
|
|
645
673
|
logStep(1, 4, "Installing skill files...");
|
package/dist/plugin.js
CHANGED
|
@@ -24,6 +24,7 @@ import path from "path";
|
|
|
24
24
|
var DEBUG = process.env.MEMORY_BANK_DEBUG === "1";
|
|
25
25
|
var DEFAULT_MAX_CHARS = 12000;
|
|
26
26
|
var GATING_MODE = process.env.MEMORY_BANK_GUARD_MODE || "warn";
|
|
27
|
+
var DOC_FIRST_MODE = process.env.MEMORY_BANK_DOC_FIRST_MODE || "off";
|
|
27
28
|
var TRUNCATION_NOTICE = `
|
|
28
29
|
|
|
29
30
|
---
|
|
@@ -292,7 +293,7 @@ async function checkMemoryBankExists(root, log) {
|
|
|
292
293
|
function getSessionMeta(sessionId, fallbackRoot) {
|
|
293
294
|
let meta = sessionMetas.get(sessionId);
|
|
294
295
|
if (!meta) {
|
|
295
|
-
meta = { rootsTouched: new Set, lastActiveRoot: fallbackRoot, notifiedMessageIds: new Set, planOutputted: false, promptInProgress: false, userMessageReceived: false, sessionNotified: false, userMessageSeq: 0 };
|
|
296
|
+
meta = { rootsTouched: new Set, lastActiveRoot: fallbackRoot, notifiedMessageIds: new Set, planOutputted: false, promptInProgress: false, userMessageReceived: false, sessionNotified: false, userMessageSeq: 0, pendingDocFirstSatisfied: false };
|
|
296
297
|
sessionMetas.set(sessionId, meta);
|
|
297
298
|
}
|
|
298
299
|
return meta;
|
|
@@ -351,13 +352,32 @@ var EXCLUDED_DIRS = [
|
|
|
351
352
|
/^\.claude\//
|
|
352
353
|
];
|
|
353
354
|
var MEMORY_BANK_PATTERN = /^memory-bank\//;
|
|
355
|
+
var DOC_FIRST_FILE_PATTERNS = [
|
|
356
|
+
/\.py$/,
|
|
357
|
+
/\.ts$/,
|
|
358
|
+
/\.tsx$/,
|
|
359
|
+
/\.js$/,
|
|
360
|
+
/\.jsx$/,
|
|
361
|
+
/\.go$/,
|
|
362
|
+
/\.rs$/,
|
|
363
|
+
/\.vue$/,
|
|
364
|
+
/\.svelte$/
|
|
365
|
+
];
|
|
354
366
|
function isDisabled() {
|
|
355
367
|
return process.env.MEMORY_BANK_DISABLED === "1" || process.env.MEMORY_BANK_DISABLED === "true";
|
|
356
368
|
}
|
|
357
|
-
function getMessageGatingState(gatingKey) {
|
|
369
|
+
function getMessageGatingState(gatingKey, sessionId, projectRoot) {
|
|
358
370
|
let state = messageGatingStates.get(gatingKey);
|
|
359
371
|
if (!state) {
|
|
360
|
-
|
|
372
|
+
let inheritDocFirst = false;
|
|
373
|
+
if (sessionId && projectRoot) {
|
|
374
|
+
const meta = sessionMetas.get(sessionId);
|
|
375
|
+
if (meta?.pendingDocFirstSatisfied) {
|
|
376
|
+
inheritDocFirst = true;
|
|
377
|
+
meta.pendingDocFirstSatisfied = false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
state = { readFiles: new Set, contextSatisfied: false, warnedThisMessage: false, docFirstSatisfied: inheritDocFirst, docFirstWarned: false };
|
|
361
381
|
messageGatingStates.set(gatingKey, state);
|
|
362
382
|
if (messageGatingStates.size > 100) {
|
|
363
383
|
const first = messageGatingStates.keys().next().value;
|
|
@@ -928,7 +948,7 @@ ${triggers.join(`
|
|
|
928
948
|
return;
|
|
929
949
|
}
|
|
930
950
|
if (event.type === "session.created") {
|
|
931
|
-
sessionMetas.set(sessionId, { rootsTouched: new Set, lastActiveRoot: projectRoot, notifiedMessageIds: new Set, planOutputted: false, promptInProgress: false, userMessageReceived: false, sessionNotified: false, userMessageSeq: 0 });
|
|
951
|
+
sessionMetas.set(sessionId, { rootsTouched: new Set, lastActiveRoot: projectRoot, notifiedMessageIds: new Set, planOutputted: false, promptInProgress: false, userMessageReceived: false, sessionNotified: false, userMessageSeq: 0, pendingDocFirstSatisfied: false });
|
|
932
952
|
const parentID = info?.parentID;
|
|
933
953
|
sessionsById.set(sessionId, { parentID });
|
|
934
954
|
log.info("Session created", { sessionId, parentID });
|
|
@@ -1152,7 +1172,7 @@ Or call: proxy_task({ subagent_type: "memory-reader", ... })`);
|
|
|
1152
1172
|
const meta = getSessionMeta(sessionID, projectRoot);
|
|
1153
1173
|
const messageKey = meta.lastUserMessageKey || "default";
|
|
1154
1174
|
const gatingKey = `${sessionID}::${messageKey}`;
|
|
1155
|
-
const gatingState = getMessageGatingState(gatingKey);
|
|
1175
|
+
const gatingState = getMessageGatingState(gatingKey, sessionID, projectRoot);
|
|
1156
1176
|
const toolLower2 = tool.toLowerCase();
|
|
1157
1177
|
const readTools = ["read"];
|
|
1158
1178
|
if (readTools.includes(toolLower2)) {
|
|
@@ -1272,6 +1292,89 @@ Or call: proxy_task({ subagent_type: "memory-reader", ... })`);
|
|
|
1272
1292
|
}
|
|
1273
1293
|
}
|
|
1274
1294
|
}
|
|
1295
|
+
if (DOC_FIRST_MODE !== "off") {
|
|
1296
|
+
const dfMeta = getSessionMeta(sessionID, projectRoot);
|
|
1297
|
+
const dfMessageKey = dfMeta.lastUserMessageKey || "default";
|
|
1298
|
+
const dfGatingKey = `${sessionID}::${dfMessageKey}`;
|
|
1299
|
+
const dfState = getMessageGatingState(dfGatingKey, sessionID, projectRoot);
|
|
1300
|
+
const dfToolLower = tool.toLowerCase();
|
|
1301
|
+
const dfWriteTools = ["write", "edit", "multiedit", "apply_patch", "patch"];
|
|
1302
|
+
if (dfWriteTools.includes(dfToolLower) && !dfState.docFirstSatisfied && !dfState.docFirstWarned) {
|
|
1303
|
+
const dfTargetPaths = extractWritePaths(dfToolLower, output.args || {});
|
|
1304
|
+
const mbCheckResults = await Promise.all(dfTargetPaths.map((p) => isMemoryBankPath(p)));
|
|
1305
|
+
const hasCodeFile = dfTargetPaths.some((p, i) => {
|
|
1306
|
+
if (mbCheckResults[i])
|
|
1307
|
+
return false;
|
|
1308
|
+
return DOC_FIRST_FILE_PATTERNS.some((pat) => pat.test(p.toLowerCase()));
|
|
1309
|
+
});
|
|
1310
|
+
if (hasCodeFile && !dfState.warnedThisMessage) {
|
|
1311
|
+
if (DOC_FIRST_MODE === "block") {
|
|
1312
|
+
log.warn("Doc-First Gate: code write blocked (no MB doc written)", {
|
|
1313
|
+
sessionID,
|
|
1314
|
+
gatingKey: dfGatingKey,
|
|
1315
|
+
tool,
|
|
1316
|
+
targetPaths: dfTargetPaths
|
|
1317
|
+
});
|
|
1318
|
+
throw new Error(`[Doc-First Gate] \u8BF7\u5148\u6C89\u6DC0\u5DE5\u4F5C\u6587\u6863\u518D\u5199\u4EE3\u7801\u3002
|
|
1319
|
+
|
|
1320
|
+
` + `\u8BF7\u7528 MemoryWriter \u5148\u8BB0\u5F55\u4F60\u8981\u505A\u4EC0\u4E48\uFF1A
|
|
1321
|
+
` + `\u2022 \u4FEE Bug / \u8E29\u5751 \u2192 learnings/YYYY-MM-DD-xxx.md
|
|
1322
|
+
` + `\u2022 \u65B0\u529F\u80FD / \u9700\u6C42 \u2192 requirements/REQ-xxx.md
|
|
1323
|
+
` + `\u2022 \u91CD\u6784 / \u4F18\u5316 \u2192 design/design-xxx.md
|
|
1324
|
+
` + `\u2022 \u7B80\u5355\u53D8\u66F4 \u2192 \u8FFD\u52A0\u5230 progress.md
|
|
1325
|
+
|
|
1326
|
+
` + `\u8C03\u7528\u65B9\u5F0F: proxy_task({ subagent_type: "memory-bank-writer", ... })
|
|
1327
|
+
` + `\u5199\u5B8C\u6587\u6863\u540E\u518D\u6267\u884C\u4EE3\u7801\u4FEE\u6539\u3002`);
|
|
1328
|
+
} else {
|
|
1329
|
+
dfState.docFirstWarned = true;
|
|
1330
|
+
log.info("Doc-First Gate: warning issued", {
|
|
1331
|
+
sessionID,
|
|
1332
|
+
gatingKey: dfGatingKey,
|
|
1333
|
+
tool,
|
|
1334
|
+
targetPaths: dfTargetPaths
|
|
1335
|
+
});
|
|
1336
|
+
client.session.prompt({
|
|
1337
|
+
path: { id: sessionID },
|
|
1338
|
+
body: {
|
|
1339
|
+
noReply: true,
|
|
1340
|
+
variant: PLUGIN_PROMPT_VARIANT,
|
|
1341
|
+
parts: [{
|
|
1342
|
+
type: "text",
|
|
1343
|
+
text: `## \u26A0\uFE0F [Doc-First] \u5EFA\u8BAE\u5148\u6C89\u6DC0\u5DE5\u4F5C\u6587\u6863\u518D\u5199\u4EE3\u7801
|
|
1344
|
+
|
|
1345
|
+
` + `\u8BF7\u7528 MemoryWriter \u5148\u8BB0\u5F55\u4F60\u8981\u505A\u4EC0\u4E48\uFF0C\u5E76\u4F5C\u4E3A\u7B2C\u4E00\u4F18\u5148\u7EA7 todo\uFF1A
|
|
1346
|
+
` + `\u2022 \u4FEE Bug / \u8E29\u5751 \u2192 learnings/YYYY-MM-DD-xxx.md
|
|
1347
|
+
` + `\u2022 \u65B0\u529F\u80FD / \u9700\u6C42 \u2192 requirements/REQ-xxx.md
|
|
1348
|
+
` + `\u2022 \u91CD\u6784 / \u4F18\u5316 \u2192 design/design-xxx.md
|
|
1349
|
+
` + `\u2022 \u7B80\u5355\u53D8\u66F4 \u2192 \u8FFD\u52A0\u5230 progress.md
|
|
1350
|
+
|
|
1351
|
+
` + `\u8C03\u7528\u65B9\u5F0F: \`proxy_task({ subagent_type: "memory-bank-writer", ... })\``
|
|
1352
|
+
}]
|
|
1353
|
+
}
|
|
1354
|
+
}).catch((err) => log.error("Failed to send doc-first warning:", String(err)));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
const markParentDocFirstSatisfied = (writerSessionID) => {
|
|
1360
|
+
const parentID = sessionsById.get(writerSessionID)?.parentID;
|
|
1361
|
+
if (!parentID)
|
|
1362
|
+
return;
|
|
1363
|
+
const parentMeta = getSessionMeta(parentID, projectRoot);
|
|
1364
|
+
const parentMsgKey = parentMeta.lastUserMessageKey;
|
|
1365
|
+
if (parentMsgKey) {
|
|
1366
|
+
const parentGatingKey = `${parentID}::${parentMsgKey}`;
|
|
1367
|
+
const parentGating = getMessageGatingState(parentGatingKey, parentID, projectRoot);
|
|
1368
|
+
parentGating.docFirstSatisfied = true;
|
|
1369
|
+
log.debug("Doc-First: parent satisfied via writer write", { writerSessionID, parentID, parentGatingKey });
|
|
1370
|
+
}
|
|
1371
|
+
const defaultGatingKey = `${parentID}::default`;
|
|
1372
|
+
const defaultState = messageGatingStates.get(defaultGatingKey);
|
|
1373
|
+
if (defaultState) {
|
|
1374
|
+
defaultState.docFirstSatisfied = true;
|
|
1375
|
+
}
|
|
1376
|
+
parentMeta.pendingDocFirstSatisfied = true;
|
|
1377
|
+
};
|
|
1275
1378
|
const isWriterAllowed = (sid) => {
|
|
1276
1379
|
if (writerSessionIDs.has(sid))
|
|
1277
1380
|
return true;
|
|
@@ -1338,6 +1441,7 @@ Or call: proxy_task({ subagent_type: "memory-reader", ... })`);
|
|
|
1338
1441
|
}
|
|
1339
1442
|
if (isWriterAllowed(sessionID)) {
|
|
1340
1443
|
log.debug("Writer agent write allowed", { sessionID, tool, targetPath });
|
|
1444
|
+
markParentDocFirstSatisfied(sessionID);
|
|
1341
1445
|
return;
|
|
1342
1446
|
}
|
|
1343
1447
|
blockWrite("not writer agent", {
|
|
@@ -1488,6 +1592,7 @@ Or call: proxy_task({ subagent_type: "memory-reader", ... })`);
|
|
|
1488
1592
|
if (await isMemoryBankPath(resolvedTarget)) {
|
|
1489
1593
|
if (isWriterAllowed(sessionID)) {
|
|
1490
1594
|
log.debug("Writer agent bash redirect allowed", { sessionID, command: command.slice(0, 100) });
|
|
1595
|
+
markParentDocFirstSatisfied(sessionID);
|
|
1491
1596
|
return;
|
|
1492
1597
|
}
|
|
1493
1598
|
blockWrite("bash redirect to memory-bank", { command: command.slice(0, 200), segment: segment.slice(0, 100) });
|
|
@@ -1503,6 +1608,7 @@ Or call: proxy_task({ subagent_type: "memory-reader", ... })`);
|
|
|
1503
1608
|
if (await isMemoryBankPath(resolved)) {
|
|
1504
1609
|
if (isWriterAllowed(sessionID)) {
|
|
1505
1610
|
log.debug("Writer agent find with dangerous flags allowed", { sessionID });
|
|
1611
|
+
markParentDocFirstSatisfied(sessionID);
|
|
1506
1612
|
return;
|
|
1507
1613
|
}
|
|
1508
1614
|
blockWrite("find with dangerous flags on memory-bank", { command: command.slice(0, 200), segment: segment.slice(0, 100) });
|
|
@@ -1519,6 +1625,7 @@ Or call: proxy_task({ subagent_type: "memory-reader", ... })`);
|
|
|
1519
1625
|
if (await isMemoryBankPath(resolved)) {
|
|
1520
1626
|
if (isWriterAllowed(sessionID)) {
|
|
1521
1627
|
log.debug("Writer agent bash write allowed", { sessionID, command: command.slice(0, 100) });
|
|
1628
|
+
markParentDocFirstSatisfied(sessionID);
|
|
1522
1629
|
return;
|
|
1523
1630
|
}
|
|
1524
1631
|
blockWrite("bash write to memory-bank", { command: command.slice(0, 200), pathArg, resolved });
|