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 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.0",
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
- state = { readFiles: new Set, contextSatisfied: false, warnedThisMessage: false };
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 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-bank-skill",
3
- "version": "7.2.0",
3
+ "version": "7.2.2",
4
4
  "description": "Memory Bank - 项目记忆系统,让 AI 助手在每次对话中都能快速理解项目上下文",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",