npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-zcode-project-memory

v0.1.0

Published

Unofficial community port of ZCode's project memory to DeepSeek Harness: project-scoped long-term memory with a save_project_memory tool plus automatic recall of project memory into the system prompt.

Readme

dsh-zcode-project-memory

English | 中文

DeepSeek Harness 的项目级长效记忆插件。模型在一次会话里沉淀下的项目教训,会在之后的新会话里被自动读回。

一行 Cordis 插件,做三件事:

  1. 注册 save_project_memory 工具 —— 写一条记忆(独立文件 + 维护索引)。
  2. 注入记忆指引到 system prompt(ctx.systemPrompt.section())—— 告诉模型何时写、写什么格式、怎么删。
  3. 注入记忆索引到运行时上下文(ctx.systemPrompt.context())—— 新会话开场就带着既有记忆的目录。

记忆按项目维度隔离:目录名由项目根目录的规范绝对路径经 sha256 派生,同一项目在任何进程、任何会话里命中同一份记忆,不同项目互不可见。

机制以 zai-org/ZCode 的实现为准复刻,DSH 侧接口以 DeepSeek Harness 官方文档为准。

非官方项目。 本项目是社区作品,与 Z.AI Co., Ltd(ZCode 的开发者)及 DeepSeek 均无隶属关系,未获其授权、赞助或背书。包名中的 zcode 仅用于说明实现来源(见上文与 NOTICE.md)。"ZCode" 是其各自权利人的商标,本项目对该名称的使用仅为描述来源。

安装

dsh plugin --profile <你的 profile> add /path/to/dsh-zcode-project-memory

package.json 声明了 dsh.bundle.patch,dsh plugin add 会自动把它加入 profile 的 bundle 层。

插件 inject 了 tools 与 systemPrompt 两个宿主服务,两者由 dsh-base 提供,因此 web / headless / tui 等继承 dsh-base 的 profile 都能生效;缺少这两个服务的部署里插件停留在 PENDING,不产生副作用。

使用

插件装好后无需任何配置,它就是模型的一个工具加两段自动注入。日常用法就是正常对话 —— 不需要记命令、不需要手动整理文件。

让模型记住

直接说,或者让模型自己判断:

记住:这个项目的 migration 必须先在 staging 跑满 24 小时才能上生产。

模型会调用 save_project_memory,写入一条记忆 + 更新索引。它也被告知在遇到非显而易见的项目约定、架构决策、调试时踩到的坑时主动保存 —— 那些「未来的人读代码也便宜地推不出来」的知识。

不想让它记的别让它记:临时的任务状态、仓库里已经写着的事实(代码结构、git 历史、AGENTS.md 的内容),指引明确排除了这两类。

让它回忆

新会话里直接问就行,记忆索引在开场就已在上下文里:

这个项目的长效记忆里有什么?
上次说的那个 migration 约束是什么?

模型看到索引行后,会按需读取对应的记忆文件拿细节。跨会话、跨进程都有效 —— 记忆落在磁盘上,不依赖某个会话还活着。

让它忘记

没有删除工具,所以直接说:

忘掉关于 migration 的那条记忆。

模型会删掉记忆文件并从 MEMORY.md 移除那一行。也可以手工删(见下文「删除记忆」)。

检查它记住了什么

记忆是纯 Markdown,随时可读可改:

M=~/.dsh/memories/projects/<项目基名>-<哈希>/memory
cat "$M/MEMORY.md"     # 这行是索引:一条记忆一行
ls "$M"                # 每条记忆一个 .md 文件

想清空某个项目的记忆,直接删掉整个 memory/ 目录即可,插件对不存在的目录安静降级。

命令

本包是纯 JavaScript(ESM),没有转译或构建步骤 —— src/ 下的源码就是发布产物,因此只有安装与测试两条命令:

| 命令 | 说明 | |---|---| | npm install | 安装唯一运行时依赖 @deepseek-ai/schemastery | | npm test | 全量测试(129 项检查) | | npm run test:subsystems | 只跑三个子系统的最小可执行用例 |

从零复现:

git clone https://github.com/ffyfox/dsh-zcode-project-memory.git && cd dsh-zcode-project-memory
npm ci        # 或 npm install
npm test      # 退出码 0 即通过

要求 Node.js >=20(见 package.json 的 engines)。CI 在 Node 20 与 22 上跑同一条命令,见 .github/workflows/ci.yml。

记忆放在哪

<DSH_HOME>/memories/projects/<项目基名>-<路径哈希前16位>/memory/
├── MEMORY.md          # 索引:一行一条,永远不放正文
└── <slug>.md          # 每条记忆一个文件,带 frontmatter

DSH_HOME 默认 ~/.dsh。记忆不写进项目仓库,项目目录保持干净,也不受 git 操作、清理或只读挂载影响。

记忆文件形态

---
name: "auth-token-refresh-gotcha"
description: "Expired tokens return 401, not 403."
metadata:
  type: project
  node_type: memory
  originSessionId: "session-…"
---

The refresh endpoint returns 401 (not 403) when the token is merely expired.

**Why:** retry logic keyed on 403 silently drops expired-token retries.

MEMORY.md 只放指针,一行一条:

- [Auth token refresh gotcha](auth-token-refresh-gotcha.md) — Expired tokens return 401, not 403.

type 取四种:user(用户是谁)、feedback(工作方式上的指导)、project(进行中的工作与约束)、reference(外部资源指针)。同一条记忆重复保存会替换自己的文件与索引行,不重复堆叠。

索引超过 200 行或 25000 字符会被截断,注入时带提示。

文件名怎么来

文件名由 key 净化得到:

| key | 文件名 | |---|---| | Auth token refresh gotcha | auth-token-refresh-gotcha.md | | 部署规则 | memory-9c31a6.md |

净化的字符类是 [a-z0-9._-]。非 ASCII 字符会被整体丢弃,因此当 key 含此字符类之外的字符时,slug 后追加原 key 的 6 位哈希 —— 否则 部署规则 / 发布前检查 / 你的哨兵 会全部塌缩成同一个 memory.md 并互相覆盖。纯 ASCII key 的文件名不受影响,已有记忆不会被改名。

删除记忆

没有删除工具。 删除是两半:删掉该记忆的文件,并且从 MEMORY.md 移除它那一行 —— 只删文件会留下悬空索引指针。注入的指引明确告诉了模型这一点,触发条件是「用户要求忘记某事」或「某条记忆被证明错误/过时」。

手工删除同理:

M=~/.dsh/memories/projects/<项目名>-<哈希>/memory
rm -f "$M/<slug>.md"
# 再从 $M/MEMORY.md 移除对应那一行

架构

src/index.js            Cordis 插件入口:工具注册 + 两处注入
├── src/store.js        记忆读写:路径派生、原子写、稳定读、并发锁
├── src/memory-format.js 记忆与索引的渲染 / 解析、slug 净化
├── src/prompt.js       两个注入面的文本渲染
└── src/subsystems/     三个可选子系统,可单独导入调用(见下)

两个注入面

| 内容 | 注入 API | 位置 | |---|---|---| | 记忆指引(何时写、格式、怎么删) | ctx.systemPrompt.section() | system prompt | | 记忆索引(MEMORY.md 文本) | ctx.systemPrompt.context() | user-role 运行时快照 |

拆成两处与 ZCode 一致:指引是稳定的规则,索引是随内容变化的数据。

子系统

src/subsystems/ 下三个模块复刻自 ZCode core/src/memory/。三者互不 import、可单独导入调用,需要彼此时通过参数注入(formatManifest / execute 回调)而非 import。

它们不被 apply() 自动启动 —— 插件的运行时行为就是「工具 + 两处注入」这三件事。

| 子系统 | 入口 | 产出 | |---|---|---| | manifest 扫描 | scanMemoryManifest({fileSystem?, rootDir, signal?})、formatMemoryManifest(manifest) | 记忆文件清单(条目数组 / 单行清单文本) | | 自动抽取 | createMemoryExtractionScheduler(execute)、evaluateMemoryExtraction(snapshot, cursor)、buildMemoryExtractionPrompt(…) | 抽取决策 + 增量范围 + 抽取 Prompt | | 后台总结 | runMemoryAgentLoop({model, tools, executeTool, …})、evaluateMemoryAgentToolPolicy(input) | 总结文本 + 轮次 + 工具调用轨迹 |

import { scanMemoryManifest, formatMemoryManifest } from 'dsh-zcode-project-memory/src/subsystems/manifest.js'

const manifest = await scanMemoryManifest({ rootDir: memoryRoot })
console.log(formatMemoryManifest(manifest))
// - [project] auth-token-refresh-gotcha.md (2026-09-22T05:05:00.000Z): Expired tokens return 401…

files 白名单包含整个 src,src/subsystems/ 随包发布。每个子系统都有独立的最小可执行用例,在 stdout 打印一行可解析 JSON({"subsystem": "manifest", …})并以退出码 0 结束。

配置

插件导出 Schemastery Config。三个字段都直接影响注入体量或单条容量,属于不同部署可能想设成不同值的量:

- id: project-memory
  config:
    indexLineLimit: 200        # 索引注入的行数上限
    indexCharacterLimit: 25000 # 索引注入的字符数上限
    maxMemoryChars: 8000       # 单条记忆正文长度上限

默认值与内置常量一致,不配置时行为不变。非法值(如 maxMemoryChars: 0)在插件加载期即失败,不静默降级。

失败行为

记忆是增强项,不能成为会话的失败源:

  • 记忆不存在或不可读 → 注入空串,不在上下文里留空占位。
  • 拿不到可解析的项目目录 → 工具返回 saved: false,两处注入返回空串。
  • 读取一律拒绝符号链接与非普通文件,5 MiB 体积上限。

写入侧:拒绝写穿符号链接、保留既有权限位、O_EXCL|O_NOFOLLOW 独占创建临时文件、fsync 后 rename;索引的读-改-写有进程内排队 + 跨进程文件锁,并发保存不丢条目。

测试

npm test

| 测试 | 覆盖 | |---|---| | test/injection.mjs | 注入面为 section + context;工具参数与输出用真实契约 | | test/config.mjs | 导出 Config、Standard Schema 接口、默认值在 schema 上、非法配置加载期失败 | | test/roundtrip.mjs | 写入 → 另起进程读回,逐字符相等;文件形态;重复保存替换;项目隔离 | | test/isolation.mjs | 路径写法归一;同名不同父目录不碰撞;写 A 不影响 B | | test/robustness.mjs | 符号链接拒绝、权限保留、原子替换、稳定读、路径安全、体积上限、并发不丢条目、exec.signal 传播、raw 工具自校验 | | test/subsystem-manifest.mjs | 递归收集、跳过索引/非 .md/失效 symlink、mtime 倒序、字段集、渲染格式、端口可注入 | | test/subsystem-extraction.mjs | 游标语义、两条跳过规则及优先级、Prompt 要素、调度器 coalescing / 不推进游标 / 关机 | | test/subsystem-summarization.mjs | 拒绝文案、工具白名单、网络/Agent/mcp 拒绝、记忆目录内写删边界、循环轮次与并行 toolCall | | test/packaging.mjs | 打包产物符合预期、源码不读取仓库内 .md、入口闭包不含开发期文件 |

端到端手工验证(需要可用的模型凭据):

cd /your/project
dsh --profile headless "把这条教训存进项目记忆:<...>,然后回复 DONE"
dsh --profile headless "这个项目的长效记忆里说了什么?引用索引行原文。"

与 ZCode 的差异

复刻以行为一致为目标,以下差异是刻意的:

| 差异 | 说明 | |---|---| | 保存入口 | ZCode 无记忆工具,模型直接用 write 写文件;本插件提供 save_project_memory,指引相应改为引导调用该工具 | | 索引维护 | ZCode 由模型手工更新 MEMORY.md;本插件在保存时由代码同步更新,两者不会不一致 | | 索引并发 | ZCode 用 expectedRevision 乐观并发,冲突即失败;本插件用排队 + 文件锁,一次保存不会因为别人同时在存另一条记忆而失败 | | # agentsMd 标题 | ZCode 把索引与 AGENTS.md 指令包在同一标题下;DSH 的指令由 dsh-agent-instructions 拥有,本插件不生成该标题 | | HTML 注释剥离 | 不引入 marked 依赖,改用围栏感知的保守近似(宁可多留,不可误删正文) | | 段落排序值 | 官方 getSectionOrder() 只接受仓库登记的具名项,仓库外插件拿不到,只能自选数值(section 950 / context 100) | | 工具命名 | 子系统的工具白名单用 DSH 实际注册的小写名(read/write/edit/bash),ZCode 的大写名在 DSH 上不会命中 |

未复刻的部分

| 项 | 原因 | |---|---| | 每轮自动抽取 + 后台总结的自动触发 | 子系统已实现且可用,但默认不挂载。每轮无条件触发会带来 token 成本叠加、并发限流压力与记忆熵增 | | 既有文件的 frontmatter 修复 | 仅在新建记忆时写入 node_type / originSessionId,不改写既有文件 |

规范符合性检查报告见 docs/conformance-report.md。

贡献

欢迎提交 Issue 与 Pull Request。

变更记录见 CHANGELOG.md。

License

本项目自身代码采用 MIT。部分实现移植自 zai-org/ZCode(Apache-2.0,Copyright 2026 Z.AI Co., Ltd),因此整体为 MIT AND Apache-2.0。

逐文件的移植对应关系见 NOTICE.md;Apache-2.0 全文见 LICENSE-APACHE-2.0,上游 NOTICE 见 NOTICE-ZCode.md。