dsh-lite-memory 0.18.35 → 0.18.37

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/README.md CHANGED
@@ -63,7 +63,9 @@ Each completed turn keeps a few lines under 「已编辑 N 个文件」, built f
63
63
 
64
64
  「记忆」页签里,搜索框右侧可以在「列表」「图谱」「宫殿」之间切换;列表、图谱或宫殿占满内容宽度,选中记忆后右侧才出现正文与归属。当前项目里点「修改」可以就地改标题和正文,保存后版本加一;别的项目和全局层只读。图谱按真实数据画,点记忆节点读正文,点标签或主题节点转成搜索,右上角可缩放。宫殿按类型分四间:习惯与规范、方法与经验、资料与入口、其他记录;一次只展开一间,每间每页 24 条,点展品只读正文。宫殿只决定怎么陈列,不改记忆、学习状态和额度。「自进化」页签只作用于当前项目,按「全部 / 候选 / 试用 / 生效 / 暂停」筛选,也能切列表与图谱;详情给出当前经验、适用范围、当前状态、学习依据和变化记录。候选可采纳、修改、放弃,试用可采纳、修改、暂停,生效可修改、暂停,暂停可恢复或查看记录。
65
65
 
66
- The brain icon in the session header opens a centered memory panel. Memory and Self-evolution sit at the top, and Display settings at the top right add two presentation-only switches, Graph and Memory palace, both off by default and remembered per project. Two tabs, Memories and Self-evolution, hold the content. It starts on the current project and can switch to another project or the global layer; switched scopes are read-only and never migrate or create state.
66
+ 「记忆」页签顶部出现「N 条保存没确认」时,一条保存相近结论的草稿在等决定。先露两条,点「查看全部 N 条」看完整队列;展开给存到哪层、什么时候用、条件、范围清单、触发词、相近条目和完整正文,底部只做「另存」或「忽略」两个动作,不在面板里合并。
67
+
68
+ The brain icon in the session header opens a centered memory panel. Memory and Self-evolution sit at the top, and Display settings at the top right add two presentation-only switches, Graph and Memory palace, both off by default and remembered per project. Two tabs, Memories and Self-evolution, hold the content. When the Memories tab shows “N saves awaiting confirmation”, a draft that resembles an existing note needs a decision. Two rows show first; View all N opens the full queue. Expanding a row shows its target layer, when it applies, conditions, scope list, triggers, similar notes and full body, and the only actions are Save as new or Ignore — the panel never merges on its own. It starts on the current project and can switch to another project or the global layer; switched scopes are read-only and never migrate or create state.
67
69
 
68
70
  On the Memories tab, the search row switches between list, graph and palace; either view fills the content width, and the body pane only appears after a memory is selected. In the current project, Edit changes a memory's title and body in place and bumps its revision on save; other projects and the global layer stay read-only. The graph is drawn from real data: a memory node opens its body, a tag or topic hub becomes a search, and the top-right controls zoom it. The palace sorts memories into four rooms by kind: Habits and conventions, Methods and experience, References and entries, and Other notes; one room opens at a time, each page holds 24 items, and an exhibit only opens its body. The palace changes presentation only, never memory, learning state or quota. The Self-evolution tab acts on the current project only, filters by All / Candidate / Trial / Active / Paused, and also offers list and graph views. It shows the current rule, scope, state, evidence and change history. Candidates can be adopted, edited or discarded; trials can be adopted, edited or paused; active rules can be edited or paused; paused rules can be resumed or inspected.
69
71
 
package/client.js CHANGED
@@ -813,6 +813,38 @@ window.__ModuleLoader__.load({
813
813
  return text.length > size ? `${text.slice(0, size)}…` : text;
814
814
  };
815
815
 
816
+ /** 待裁决先露两条;「查看全部 N 条」后按队列原样全列,不截断。 */
817
+ const conflictVisibleRows = (queued, all) => (all ? queued : queued.slice(0, 2));
818
+
819
+ /** 面板展开区一次给全:层级、适用条件、范围清单、触发词、相近条目、完整正文。 */
820
+ const conflictFactsOf = (row) => {
821
+ const draft = row && row.draft && typeof row.draft === 'object' ? row.draft : {};
822
+ const conditions = (Array.isArray(draft.conditions) ? draft.conditions : []).map((item) => {
823
+ if (!item || typeof item !== 'object') return '';
824
+ const key = String(item.key || item.name || '').trim();
825
+ const value = String(item.value || '').trim();
826
+ return key && value ? `${key}=${value}` : key || value;
827
+ }).filter(Boolean).join(' · ');
828
+ const selection = draft.selection && typeof draft.selection === 'object' ? draft.selection : null;
829
+ let selectionText = '';
830
+ if (selection && selection.mode === 'complete') selectionText = `清单完整${selection.artifactKey ? ` · ${selection.artifactKey}` : ''}`;
831
+ else if (selection && selection.mode === 'examples') {
832
+ const items = Array.isArray(selection.items) ? selection.items.filter(Boolean) : [];
833
+ selectionText = items.length > 0 ? `举例:${items.join('、')}` : '举例';
834
+ }
835
+ const triggers = Array.isArray(draft.triggers) ? draft.triggers.filter(Boolean).join('、') : '';
836
+ const near = Array.isArray(row && row.candidates) ? row.candidates.map((item) => item && item.title).filter(Boolean) : [];
837
+ return {
838
+ scope: row && row.scope === 'global' ? '总记忆' : '本项目',
839
+ when: typeof draft.when === 'string' ? draft.when : '',
840
+ conditions,
841
+ selection: selectionText,
842
+ triggers,
843
+ near: near.join('、'),
844
+ body: String(draft.body || draft.summary || '(没有正文)'),
845
+ };
846
+ };
847
+
816
848
  /** 显示设置只决定画不画,不改学习状态;按项目 key 存在本机。 */
817
849
  const DISPLAY_DEFAULTS = Object.freeze({ graph: false, palace: false });
818
850
  const displayKeyOf = (projectKey) => `lite-memory:display:${projectKey || 'default'}`;
@@ -2158,6 +2190,7 @@ window.__ModuleLoader__.load({
2158
2190
  const [hubFilter, setHubFilter] = React.useState(null);
2159
2191
  const [checkup, setCheckup] = React.useState(false);
2160
2192
  const [expandedConflict, setExpandedConflict] = React.useState('');
2193
+ const [conflictAll, setConflictAll] = React.useState(false);
2161
2194
  const alive = React.useRef(true);
2162
2195
  const generation = React.useRef(0);
2163
2196
  const scopeRef = React.useRef('');
@@ -2192,6 +2225,7 @@ window.__ModuleLoader__.load({
2192
2225
  setDetail(null);
2193
2226
  setDetailNote('');
2194
2227
  setShowReason(false);
2228
+ setConflictAll(false);
2195
2229
  } else {
2196
2230
  setFailed(true);
2197
2231
  setNote(failure(result, '读不到记忆'));
@@ -2225,6 +2259,7 @@ window.__ModuleLoader__.load({
2225
2259
  setShowSettings(false);
2226
2260
  setHubFilter(null);
2227
2261
  setExpandedConflict('');
2262
+ setConflictAll(false);
2228
2263
  }, [selectedKey]);
2229
2264
  React.useEffect(() => {
2230
2265
  setMemoryView((value) => viewModeOf(display, value));
@@ -2732,14 +2767,17 @@ window.__ModuleLoader__.load({
2732
2767
  const evolutionViewActive = graphViewOf(display.graph, evolutionView);
2733
2768
 
2734
2769
  const conflictReadOnly = !memoryOn || (!globalView && selectedKey !== currentKey);
2735
- const draftScope = (row) => (row.scope === 'global' ? '总记忆' : '本项目');
2770
+ const visibleConflicts = conflictVisibleRows(queued, conflictAll);
2736
2771
  const conflictBar = queued.length === 0 ? null : h('div', {
2737
2772
  style: { display: 'flex', flexDirection: 'column', gap: '3px', padding: '7px 9px 8px', borderBottom: '1px solid var(--dsw-alias-border-l1)', flex: '0 0 auto' },
2738
2773
  },
2739
- h('div', { style: { ...PANEL_MUTED, lineHeight: '18px' } }, `${queued.length} 条保存没确认${conflictReadOnly ? ' · 只读' : ''}`),
2740
- queued.slice(0, 2).map((row) => {
2774
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 } },
2775
+ h('span', { style: { ...PANEL_MUTED, lineHeight: '18px', flex: 1, minWidth: 0 } }, `${queued.length} 条保存没确认${conflictReadOnly ? ' · 只读' : ''}`),
2776
+ queued.length > 2 ? h(Action, { disabled: busy, onClick: () => setConflictAll((value) => !value) }, conflictAll ? '收起' : `查看全部 ${queued.length} 条`) : null,
2777
+ ),
2778
+ visibleConflicts.map((row) => {
2741
2779
  const open = expandedConflict === row.id;
2742
- const draft = row.draft || {};
2780
+ const facts = conflictFactsOf(row);
2743
2781
  return h('div', {
2744
2782
  key: row.id || row.title,
2745
2783
  style: { display: 'flex', flexDirection: 'column', gap: '2px', minWidth: 0 },
@@ -2759,11 +2797,15 @@ window.__ModuleLoader__.load({
2759
2797
  conflictReadOnly ? null : h(Action, { disabled: busy, onClick: () => resolveQueued(row, 'drop') }, '忽略'),
2760
2798
  ),
2761
2799
  open ? h('div', {
2762
- style: { ...PANEL_MUTED, padding: '2px 0 4px', lineHeight: '18px', overflowWrap: 'anywhere' },
2800
+ style: { ...PANEL_MUTED, padding: '2px 0 4px', lineHeight: '18px', overflowWrap: 'anywhere', maxHeight: '180px', overflowY: 'auto' },
2763
2801
  },
2764
- h('div', null, `范围:${draftScope(row)}`),
2765
- draft.when ? h('div', null, `适用:${draft.when}`) : null,
2766
- h('div', null, String(draft.body || draft.summary || '(没有正文)').slice(0, 400)),
2802
+ h('div', null, `存到:${facts.scope}`),
2803
+ facts.when ? h('div', null, `什么时候用:${facts.when}`) : null,
2804
+ facts.conditions ? h('div', null, `条件:${facts.conditions}`) : null,
2805
+ facts.selection ? h('div', null, `适用范围:${facts.selection}`) : null,
2806
+ facts.triggers ? h('div', null, `触发词:${facts.triggers}`) : null,
2807
+ facts.near ? h('div', null, `相近:${facts.near}`) : null,
2808
+ h('div', { style: { whiteSpace: 'pre-wrap', color: 'var(--dsw-alias-label-primary)' } }, facts.body),
2767
2809
  ) : null);
2768
2810
  }));
2769
2811
  const memoryListPane = h('div', {
@@ -3014,7 +3056,7 @@ window.__ModuleLoader__.load({
3014
3056
 
3015
3057
  // 测试缝:Node 里验引用投影,浏览器里没有任何副作用。
3016
3058
  if (typeof globalThis.__LITE_MEMORY_TEST__ === 'function') {
3017
- globalThis.__LITE_MEMORY_TEST__({ metaOf, citationOf, citationsOfMeta, citationsOfTurn, dedupe, undoRefOf, graphOf, ruleGraphOf, processingStatusOf, currentBodyOf, historyDiffsOf, MemoryPanel, MemoryGraph, MemoryPalace, TurnMemory, RefSection, displayKeyOf, readDisplaySettings, writeDisplaySettings, graphViewOf, viewModeOf, filterRecords, projectionOf, failureStateOf, evidenceTextOf, evidenceMetaOf, palaceRoomsOf, palaceRoomPageOf, palacePageWindow, palaceToggleOf, palaceMotionOf, palaceRoomIdOf, refMetaOf, explainKeyOf, explainModeOf, setExplainMode, recallLineOf, agoLabel, checkupOf, refSectionRowsOf, refDetailTitleOf, versionLine, titleCountsOf, rowKeyOf, rowKeepFlags, Row });
3059
+ globalThis.__LITE_MEMORY_TEST__({ metaOf, citationOf, citationsOfMeta, citationsOfTurn, dedupe, undoRefOf, graphOf, ruleGraphOf, processingStatusOf, currentBodyOf, historyDiffsOf, MemoryPanel, MemoryGraph, MemoryPalace, TurnMemory, RefSection, displayKeyOf, readDisplaySettings, writeDisplaySettings, graphViewOf, viewModeOf, filterRecords, projectionOf, failureStateOf, evidenceTextOf, evidenceMetaOf, palaceRoomsOf, palaceRoomPageOf, palacePageWindow, palaceToggleOf, palaceMotionOf, palaceRoomIdOf, refMetaOf, explainKeyOf, explainModeOf, setExplainMode, recallLineOf, agoLabel, checkupOf, refSectionRowsOf, refDetailTitleOf, versionLine, titleCountsOf, rowKeyOf, rowKeepFlags, Row, conflictVisibleRows, conflictFactsOf });
3018
3060
  }
3019
3061
 
3020
3062
  return {
@@ -0,0 +1,8 @@
1
+ # 0.18.36:待裁决看全,再决定另存还是忽略
2
+
3
+ - 记忆面板的“待裁决”不再只露两条:先显示最近两条,点“查看全部 N 条”按队列原样展开,最多看到全部待办。
4
+ - 每一条展开后给全审阅信息:存到总记忆还是本项目、什么时候用、条件键值、范围清单或举例、触发词、相近条目,以及完整正文,不再截 400 字。
5
+ - 动作仍然只有“另存”和“忽略”。另存按草稿原样重放并涨版本,忽略只删待办;草稿被后来的反馈替换后,旧版本点击仍会被拒。
6
+ - 审阅信息的整理逻辑抽成纯函数,客户端测试覆盖先露两条、查看全部不截断、字段完整与空正文兜底。
7
+
8
+ 验证:`npm test` 全绿;`node ~/.dsh/skills/ui-restraint/scripts/ui-copy-lint.mjs .` 无新增错误;`style-lint` 与中文界面检查通过。
@@ -0,0 +1,8 @@
1
+ # 0.18.37:npm 提醒先看当前项目层
2
+
3
+ - 动作前的 npm 提醒不再只查全局层:先查当前项目层的现行记忆,再查全局层,项目规则优先。
4
+ - 本机流程按记忆分层规矩留在项目层,也能在 `npm whoami` / `npm publish` 前拦一次,而不是因为没进全局层就失效。
5
+ - 同一条提醒仍然每轮最多一次,已交付过的条目不会再提示;项目记忆关闭时行为不变。
6
+ - 补回归:项目层与全局层各放一条 npm 规则,验证项目层优先且正文送达。
7
+
8
+ 验证:`npm test` 全绿;`npm run bench` / `npm run bench:evolution` 均 exit 0;`style-lint` 通过。
package/index.js CHANGED
@@ -2671,8 +2671,16 @@ export function apply(ctx, config) {
2671
2671
  const held = prepared.get(agent);
2672
2672
  if (!held || held.npmHinted) return undefined;
2673
2673
  if (!memoryOnForAgent(agent)) return undefined;
2674
- const index = loadIndex(globalScope());
2675
- const row = (index.entries || []).find((entry) => {
2674
+ /** 本机流程按规矩留在项目层;全局层是兜底,先看当前项目再看全局。 */
2675
+ const scopes = [];
2676
+ try {
2677
+ const project = scopeOf(cwdOf({ agent }));
2678
+ if (project && project.confirmed !== false && project.key !== 'global') scopes.push(project);
2679
+ } catch { /* 项目根还没确认时只看全局 */ }
2680
+ scopes.push(globalScope());
2681
+ const row = scopes.flatMap((scope) => {
2682
+ try { return loadIndex(scope).entries || []; } catch { return []; }
2683
+ }).find((entry) => {
2676
2684
  if (!entry || entry.archived || entry.origin === 'project_evolution') return false;
2677
2685
  /** 过期/被取代的规则不能再在动作前拦人。 */
2678
2686
  if (view.lifecycleStatusOf(entry)) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-lite-memory",
3
- "version": "0.18.35",
3
+ "version": "0.18.37",
4
4
  "description": "On-demand memory for the DeepSeek Harness: nothing resident, at most three small cards on a real hit, bodies read on demand.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -28,7 +28,7 @@ assert.equal(panel.name, 'shell.overlay');
28
28
  assert.equal(panel.order, 30);
29
29
 
30
30
  const EMPTY_CHILDREN = [];
31
- const { metaOf, citationOf, citationsOfMeta, citationsOfTurn, dedupe, undoRefOf, graphOf, ruleGraphOf, processingStatusOf, currentBodyOf, historyDiffsOf, MemoryPanel, MemoryGraph, displayKeyOf, readDisplaySettings, writeDisplaySettings, graphViewOf, viewModeOf, filterRecords, projectionOf, failureStateOf, evidenceTextOf, evidenceMetaOf, MemoryPalace, palaceRoomsOf, palaceRoomPageOf, palacePageWindow, palaceToggleOf, palaceMotionOf, palaceRoomIdOf, TurnMemory, refMetaOf, explainKeyOf, explainModeOf, setExplainMode, recallLineOf, agoLabel, checkupOf, refSectionRowsOf, refDetailTitleOf, versionLine, titleCountsOf, rowKeyOf, rowKeepFlags } = seen;
31
+ const { metaOf, citationOf, citationsOfMeta, citationsOfTurn, dedupe, undoRefOf, graphOf, ruleGraphOf, processingStatusOf, currentBodyOf, historyDiffsOf, MemoryPanel, MemoryGraph, displayKeyOf, readDisplaySettings, writeDisplaySettings, graphViewOf, viewModeOf, filterRecords, projectionOf, failureStateOf, evidenceTextOf, evidenceMetaOf, MemoryPalace, palaceRoomsOf, palaceRoomPageOf, palacePageWindow, palaceToggleOf, palaceMotionOf, palaceRoomIdOf, TurnMemory, refMetaOf, explainKeyOf, explainModeOf, setExplainMode, recallLineOf, agoLabel, checkupOf, refSectionRowsOf, refDetailTitleOf, versionLine, titleCountsOf, rowKeyOf, rowKeepFlags, conflictVisibleRows, conflictFactsOf } = seen;
32
32
 
33
33
  // 图谱只按真实数据画:共享标签成枢纽,显式关联成边,位置由 id 决定。
34
34
  const graph = graphOf([
@@ -368,3 +368,31 @@ assert.equal(recallLineOf({ reason: 'made_up', cards: 0, matched: 0 }), '');
368
368
  assert.doesNotMatch(recallLineOf({ reason: 'needs_context', cards: 0, matched: 1 }), /needs_context|reasonCode/);
369
369
  console.log('client.test.mjs: 22 passed + palace');
370
370
 
371
+
372
+
373
+ // 待裁决面板:先露两条,查看全部后不截断;展开区给全层级、条件、范围、触发词与完整正文。
374
+ const pendingRows = [
375
+ { id: 'cf_1', scope: 'global', title: '一', draft: { body: '正文一' } },
376
+ { id: 'cf_2', scope: 'project', title: '二', draft: { body: '正文二' } },
377
+ { id: 'cf_3', scope: 'project', title: '三', draft: { body: '正文三' } },
378
+ ];
379
+ assert.deepEqual(conflictVisibleRows(pendingRows, false).map((row) => row.id), ['cf_1', 'cf_2']);
380
+ assert.deepEqual(conflictVisibleRows(pendingRows, true).map((row) => row.id), ['cf_1', 'cf_2', 'cf_3']);
381
+ const facts = conflictFactsOf({
382
+ scope: 'global',
383
+ candidates: [{ title: '旧结论' }, { title: '另一条' }],
384
+ draft: {
385
+ title: '发布窗口', summary: '摘要', body: '周四晚上再发版。', when: '发版前',
386
+ conditions: [{ key: 'phase', value: 'release' }], triggers: ['发版', '发布'],
387
+ selection: { mode: 'complete', artifactKey: 'release' },
388
+ },
389
+ });
390
+ assert.equal(facts.scope, '总记忆');
391
+ assert.equal(facts.when, '发版前');
392
+ assert.equal(facts.conditions, 'phase=release');
393
+ assert.equal(facts.selection, '清单完整 · release');
394
+ assert.equal(facts.triggers, '发版、发布');
395
+ assert.equal(facts.near, '旧结论、另一条');
396
+ assert.equal(facts.body, '周四晚上再发版。');
397
+ assert.equal(conflictFactsOf({ scope: 'project', draft: { selection: { mode: 'examples', items: ['a.md', 'b.md'] } } }).selection, '举例:a.md、b.md');
398
+ assert.equal(conflictFactsOf({ draft: { body: '' } }).body, '(没有正文)');
@@ -846,6 +846,40 @@ console.log('recall.test.mjs: ok');
846
846
  assert.ok(scopeNpm.key, '项目层不受影响');
847
847
  }
848
848
 
849
+ // A27b:npm 提醒先看当前项目层,再看全局层;本机流程留在项目层也能在动作前拦人。
850
+ {
851
+ const cwdNpmProject = mkdir('npm-guard-project');
852
+ const scopeNpmProject = store.scopeOf(cwdNpmProject);
853
+ store.setRecallMode(scopeNpmProject, 'adaptive', { operationId: 'npm-guard-project-mode' });
854
+ assert.ok(store.upsert(global, {
855
+ title: 'npm 全局登录提醒',
856
+ summary: '全局层的 npm 登录说明',
857
+ body: '全局层:npm login 会误导,改用 dsh-npm。',
858
+ tags: ['npm'],
859
+ applicability: 'all_in_scope',
860
+ }).record);
861
+ assert.ok(store.upsert(scopeNpmProject, {
862
+ title: 'npm 项目发布前先看凭据',
863
+ summary: '这个项目的 npm 发布步骤',
864
+ body: '项目层:先读 Keychain 位置,再用 dsh-npm publish,不要 npm whoami。',
865
+ tags: ['npm', '发布'],
866
+ applicability: 'all_in_scope',
867
+ }).record);
868
+ const guards = [];
869
+ const agent = makeAgent('agent-npm-project', cwdNpmProject);
870
+ agent.ctx = {
871
+ tools: { guard: (fn) => { guards.push(fn); return () => {}; } },
872
+ effect: (fn) => fn(),
873
+ };
874
+ const runtime = boot();
875
+ runtime.handlers.get('agent/created').at(-1)({ agent });
876
+ const sig = signal();
877
+ await runtime.preStep({ agent, signal: sig, turn: 'npm-project-1' }, enter(human('先看下环境状态')));
878
+ const blocked = guards[0]({ name: 'bash', callId: 'call-npm-project', arguments: { command: 'npm publish' } });
879
+ assert.match(String(blocked), /项目发布前先看凭据/, '项目层规则优先于全局层');
880
+ assert.match(String(blocked), /Keychain/, '给项目层正文');
881
+ }
882
+
849
883
  // A28:零命中的同一轮里,关键动作发生后补查一次;补到后不再重复注入。
850
884
  {
851
885
  const cwdActionKey = mkdir('action-key');