@max-null/dsh-memory 0.19.1 → 0.20.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/README.md CHANGED
@@ -76,6 +76,18 @@ npm install @max-null/dsh-memory
76
76
 
77
77
  两个根都可用 config 覆盖(`globalRoot` / `projectRoot`)。`memory_list` / `memory_search` 不带 `namespace` 过滤时会同时查两层。旧版双重前缀文件名(`memory_project_memory_project_<hash>.json`)在打开时自动迁移为规范名。
78
78
 
79
+ ## 主题(topic,0.20.0)
80
+
81
+ `namespace` 管「在哪生效」,`topic` 管「这条讲什么」,两者正交:
82
+
83
+ | 字段 | 回答的问题 | 取值 |
84
+ |---|---|---|
85
+ | `namespace` | 换个项目还成立吗 | `global` / `project` |
86
+ | `kind` | 是记忆还是提示词模板 | `fact` / `prompt` |
87
+ | `topic` | 这条记忆讲什么 | 自由字符串:`见闻` / `约定` / `坑` / `偏好` / `事实` / `判据` … |
88
+
89
+ `memory_save` 传 `topic` 落库,`memory_list` / `memory_search` 传 `topic` 切片。留空或省略即「未分类」。用它把「自己出去逛到的」(`见闻`)和「一起干活时记下的」分开翻,不必再往正文里手写前缀。
90
+
79
91
  ## 跨工作区可见性(0.10.0 / 0.11.0)
80
92
 
81
93
  project 记忆按**会话工作区**分文件存放,所以「工作区」就是可见性的边界。0.10.0 起补了三个方向——针对的是同一个盲区:**不知道存在**(检索是有意图的动作,搜不出自己不知道存在的东西)。
package/dist/engine.d.ts CHANGED
@@ -81,6 +81,13 @@ export interface MemoryRecord {
81
81
  lastUsedAt?: number;
82
82
  /** 记录类别(缺省 'fact')。 */
83
83
  kind?: MemoryKind;
84
+ /**
85
+ * 记忆主题(0.20.0):见闻 / 约定 / 坑 / 偏好 / 事实 / 判据 …
86
+ *
87
+ * 与 `kind` 正交:`kind` 分「记忆还是提示词模板」,`topic` 分「这条记忆讲什么」。
88
+ * 存自由字符串,方便「只翻我出去逛到的那些」这类切片。
89
+ */
90
+ topic?: string;
84
91
  /** prompt 记录元数据(仅 kind==='prompt')。 */
85
92
  meta?: PromptMetaIndex;
86
93
  /**
@@ -93,6 +100,8 @@ export interface MemoryWrite {
93
100
  content: string;
94
101
  namespace?: MemoryNamespace;
95
102
  keywords?: string[];
103
+ /** 记忆主题(0.20.0):见闻 / 约定 / 坑 / 偏好 / 事实 / 判据 …;省略表示未分类。 */
104
+ topic?: string;
96
105
  /** 写入来源(2026-09-15);缺省 'agent'(主模型写入)。面板等人工路径传 'human'。 */
97
106
  source?: MemorySource;
98
107
  /**
@@ -115,6 +124,8 @@ export interface MemoryFilter {
115
124
  quarantined?: boolean;
116
125
  /** 记录类别过滤(0.6.0);缺省不过滤(工具层显式传 kind='fact' 保持旧行为)。 */
117
126
  kind?: MemoryKind;
127
+ /** 主题过滤(0.20.0):只取这个 topic 的记录;缺省不过滤。 */
128
+ topic?: string;
118
129
  /**
119
130
  * 更新时间下界 / 上界(0.12.0;epoch ms,含端点)。
120
131
  *
package/dist/engine.js CHANGED
@@ -145,10 +145,11 @@ function toRecord(id, block) {
145
145
  ...block.stale === undefined ? {} : { stale: block.stale },
146
146
  ...block.staleReason === undefined ? {} : { staleReason: block.staleReason },
147
147
  };
148
- // 0.6.0 模板库字段(缺省 fact;prompt 元数据透传)
148
+ // 0.6.0 模板库字段(缺省 fact;prompt 元数据透传);0.20.0 起带主题
149
149
  return {
150
150
  ...base,
151
151
  ...block.kind === undefined ? {} : { kind: block.kind },
152
+ ...block.topic === undefined ? {} : { topic: block.topic },
152
153
  ...block.meta === undefined ? {} : { meta: block.meta },
153
154
  };
154
155
  }
@@ -758,6 +759,8 @@ export class MemoryEngine extends Service {
758
759
  ...input.injected === undefined ? {} : { injectedAuto: false },
759
760
  // 有效性锚点(可选):值变了这条记忆会被标 stale,不再被当成仍然正确
760
761
  ...input.anchor === undefined ? {} : { anchor: input.anchor },
762
+ // 主题(0.20.0):省略即未分类,与 `kind`(fact/prompt)正交
763
+ ...input.topic === undefined || input.topic.trim() === '' ? {} : { topic: input.topic.trim() },
761
764
  ...verdict.quarantined
762
765
  ? { quarantined: true, ...verdict.reason === undefined ? {} : { quarantineReason: verdict.reason } }
763
766
  : {},
@@ -778,7 +781,8 @@ export class MemoryEngine extends Service {
778
781
  // 0.12.0:按更新时间筛——与注入排序、注入行的日期标记同一个依据,不制造第二个「新」的定义
779
782
  && (filter?.after === undefined || record.updatedAt >= filter.after)
780
783
  && (filter?.before === undefined || record.updatedAt <= filter.before)
781
- && (filter?.kind === undefined || (record.kind ?? 'fact') === filter.kind));
784
+ && (filter?.kind === undefined || (record.kind ?? 'fact') === filter.kind)
785
+ && (filter?.topic === undefined || record.topic === filter.topic));
782
786
  }
783
787
  /**
784
788
  * 关键词检索。每次成功返回的检索都记一条查询日志(⑥ 2026-09-15)——命中与否都记:
package/dist/index.js CHANGED
@@ -125,6 +125,7 @@ const RECORD_SCHEMA = {
125
125
  injected: { type: 'boolean', required: true },
126
126
  content: { type: 'string', required: true },
127
127
  keywords: { type: 'array', required: true, items: { type: 'string' } },
128
+ topic: { type: 'string', description: '主题(0.20.0):见闻 / 约定 / 坑 / 偏好 / 事实 / 判据 …;缺省表示未分类。' },
128
129
  createdAt: { type: 'number', required: true },
129
130
  updatedAt: { type: 'number', required: true },
130
131
  lastUsedAt: { type: 'number' },
@@ -296,6 +297,7 @@ function recordValue(record) {
296
297
  injected: record.injected,
297
298
  content: record.content,
298
299
  keywords: record.keywords,
300
+ ...record.topic === undefined ? {} : { topic: record.topic },
299
301
  createdAt: record.createdAt,
300
302
  updatedAt: record.updatedAt,
301
303
  ...record.lastUsedAt === undefined ? {} : { lastUsedAt: record.lastUsedAt },
@@ -587,6 +589,7 @@ export async function apply(ctx, config) {
587
589
  content: { type: 'string', required: true, description: 'Plaintext memory content.' },
588
590
  namespace: { type: 'string', enum: ['global', 'project'], description: 'Where it applies; defaults to global.' },
589
591
  keywords: { type: 'array', items: { type: 'string' }, description: 'Explicit searchable anchors for memory_search. Give several angles (synonyms, abbreviations, zh/en) or it will not be found later. 多角度关键词(同义词、缩写、中英),否则将来检索不到它。' },
592
+ topic: { type: 'string', description: 'What this memory is about, one short word: 见闻 (things noticed while wandering) / 约定 (standing agreement) / 坑 (a pitfall hit) / 偏好 (a preference) / 事实 / 判据. Orthogonal to namespace. 主题:见闻 / 约定 / 坑 / 偏好 / 事实 / 判据;与 namespace 正交,缺省表示未分类。' },
590
593
  injected: { type: 'boolean', description: 'Set true to make this memory resident in every turn\'s context, as a deliberate decision that no longer auto-expires. Reserve it for rules, standing agreements and judgement criteria — the kind that must apply even when nobody thinks to search for them. Facts, references and case notes stay search-only (omit it). 设为 true 让这条记忆每轮常驻上下文,且视为有意决定、不再被自动淘汰。只用于规则、长期约定与判据——那些「没人想起来搜也必须生效」的记忆;事实、参考与案例留作按需检索(省略即可)。' },
591
594
  anchor: {
592
595
  type: 'object',
@@ -609,6 +612,7 @@ export async function apply(ctx, config) {
609
612
  content: args.content,
610
613
  ...args.namespace === undefined ? {} : { namespace: args.namespace },
611
614
  ...args.keywords === undefined ? {} : { keywords: args.keywords },
615
+ ...args.topic === undefined ? {} : { topic: args.topic },
612
616
  ...args.injected === undefined ? {} : { injected: args.injected },
613
617
  // 参数已由 tool schema 校验,这里的窄化只为过类型
614
618
  ...args.anchor === undefined
@@ -632,6 +636,7 @@ export async function apply(ctx, config) {
632
636
  namespace: { type: 'string', enum: ['global', 'project'], description: 'Restrict to one namespace. 限定单个命名空间.' },
633
637
  status: { type: 'string', enum: ['suggested', 'approved'], description: 'Restrict to one review status. 限定审核状态(suggested=待审核 / approved=已审核).' },
634
638
  injected: { type: 'boolean', description: 'Restrict by the persistent-injection switch. 按常驻注入开关过滤.' },
639
+ topic: { type: 'string', description: 'Restrict to one topic (见闻 / 约定 / 坑 / 偏好 / 事实 / 判据 …). 按主题过滤(0.20.0).' },
635
640
  after: { type: 'string', description: 'Only memories updated at/after this time: `YYYY-MM-DD`, or a relative form like `7d` / `30d`. 只返回此后更新过的记忆(`YYYY-MM-DD`,或相对形式 `7d` / `30d`).' },
636
641
  before: { type: 'string', description: 'Only memories updated at/before this time (same forms as `after`). 只返回此前更新过的记忆(写法同 after).' },
637
642
  },
@@ -647,6 +652,7 @@ export async function apply(ctx, config) {
647
652
  ...args.namespace === undefined ? {} : { namespace: args.namespace },
648
653
  ...args.status === undefined ? {} : { status: args.status },
649
654
  ...args.injected === undefined ? {} : { injected: args.injected },
655
+ ...args.topic === undefined ? {} : { topic: args.topic },
650
656
  ...after === undefined ? {} : { after },
651
657
  ...before === undefined ? {} : { before },
652
658
  }, execProjectCwd(exec)).then(records => records.map(recordValue));
@@ -661,6 +667,7 @@ export async function apply(ctx, config) {
661
667
  namespace: { type: 'string', enum: ['global', 'project'], description: 'Restrict to one namespace. 限定单个命名空间.' },
662
668
  status: { type: 'string', enum: ['suggested', 'approved'], description: 'Restrict to one review status. 限定审核状态.' },
663
669
  kind: { type: 'string', enum: ['fact', 'prompt'], description: 'Restrict by record kind — fact (memories, default) or prompt (templates; use prompt_search instead). 按记录类别过滤:fact=记忆(默认)/ prompt=模板(模板请用 prompt_search).' },
670
+ topic: { type: 'string', description: 'Restrict to one topic (见闻 / 约定 / 坑 / 偏好 / 事实 / 判据 …). 按主题过滤(0.20.0).' },
664
671
  limit: { type: 'integer', default: DEFAULT_SEARCH_LIMIT, description: 'Maximum hits to return (default 20; 0 = no limit; negative falls back to 20). 最多返回条数(默认 20;0 = 不限;负数按 20).' },
665
672
  },
666
673
  output: {
@@ -676,6 +683,7 @@ export async function apply(ctx, config) {
676
683
  ...args.namespace === undefined ? {} : { namespace: args.namespace },
677
684
  ...args.status === undefined ? {} : { status: args.status },
678
685
  ...args.kind === undefined ? {} : { kind: args.kind },
686
+ ...args.topic === undefined ? {} : { topic: args.topic },
679
687
  }, execProjectCwd(exec)).then(hits => (limit === 0 ? hits : hits.slice(0, limit)).map(hitValue));
680
688
  },
681
689
  presentCall: args => present('Search memory', 'read', args.query),
package/dist/schema.d.ts CHANGED
@@ -63,6 +63,7 @@ export declare const blockSchema: z.ZodObject<{
63
63
  fact: "fact";
64
64
  prompt: "prompt";
65
65
  }>>;
66
+ topic: z.ZodOptional<z.ZodString>;
66
67
  meta: z.ZodOptional<z.ZodObject<{
67
68
  seq: z.ZodOptional<z.ZodNumber>;
68
69
  name: z.ZodString;
package/dist/schema.js CHANGED
@@ -45,6 +45,14 @@ export const blockSchema = z.object({
45
45
  lastUsedAt: z.number().optional(),
46
46
  vector: z.array(z.number()).optional(),
47
47
  kind: z.enum(['fact', 'prompt']).optional(),
48
+ /**
49
+ * 记忆主题(0.20.0):见闻 / 约定 / 坑 / 偏好 / 事实 / 判据 … 自由字符串。
50
+ *
51
+ * 与 `kind` 正交——`kind` 分「记忆还是提示词模板」(记录类别),`topic` 分「这条记忆讲什么」
52
+ * (内容主题)。用自由字符串而不是枚举:新增主题不该要求改 schema,而枚举一旦写死,
53
+ * 存量文件里出现未登记的值会被门三判成不合规、进而整块读不出来。
54
+ */
55
+ topic: z.string().optional(),
48
56
  meta: z.object({
49
57
  seq: z.number().optional(),
50
58
  name: z.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-memory",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "description": "Cross-session plaintext memory plugin for the DeepSeek Harness — deterministic BM25 recall, writes take effect immediately, signal-driven injection, no embeddings",
5
5
  "license": "MIT",
6
6
  "publishConfig": {