Skip to content

[Feature] agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply,全留审计 #231

Closed

Description

@modusensus

背景

讨论 #164 的「第三腿触发」:dream 自动整理与查询按需综合之外,缺一个 agent 自己决定调用的整理入口——长段工作收尾时,agent 发起「整理当前上下文、与已有记忆比对去重、沉淀 document」(document 型见姊妹 issue)。

形态(#164 已对齐)

  1. dryRun:返回比对报告(候选新条 vs 库内相似条),不写库;
  2. agent 判断报告后调 apply 落库;筛除项进归档不删;
  3. 调用全程留审计回执——复用 dream_runs 的 receipt 语义,不新建审计面。

边界

  • 现有 9 工具与命令面均无此入口,不重复造:新工具一个(dryRun/apply 两档)或一个工具带 mode 参数,倾向后者;
  • opt-in 默认关(新行为开关惯例),settings.js 白名单注册 + lightMode 考虑;
  • 上下文大幅精简前的「关键连续性」整理是其主场景(对照 2608.22752 压缩悬崖:任务状态是蒸馏管线覆盖不到的部分)。

Activity

  1. heptaspirit commented on Sep 19, 2026

    @heptaspirit
    Collaborator

    对照 #249 的提案时意识到一个问题:单独实现该主动整理接口依然不能解决“AI为什么什么时候调用它”的问题,至少需要在 #249 中的能力说明完善后,通过注入形式教会 AI 使用。
    所以对该 Feature 提出几个新建议:

    1. 接口实现后暂不对 agent 暴露,暂不进入工具列表
    2. 暂不对其单独实现 opt-in,默认不显示
    3. 具体的注入和对 agent 暴露的形式与 [Feature] 注入形态:能力说明 + 指针 + 分时机注入(设计提案) #249 一同实现,本 Feature 仅实现功能本身并为后续开发铺路
  2. modusensus commented on Sep 19, 2026

    @modusensus
    MemberAuthor

    @heptaspirit 顺着 #230 问一句 #231(agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply)的归属。

    这两条是你在 #164 里提的同一份设计的两个半边——「产物分两型」与「触发加第三条腿」。你认领了 #230,所以想确认 #231 是你打算一起做,还是留给我们这边接?

    如果由我们接,会按你在 #164 里定的口径走(先 dryRun 出比对报告、agent 判断后再 apply、调用全留审计),实现前把接口形态贴上来给你过一遍。如果由你一起做,我们就不动,只在 review 时配合。

  3. heptaspirit commented on Sep 19, 2026

    @heptaspirit
    Collaborator

    @modusensus 本 Issue 我们认领,接在 #230 之后开发。
    需要对的一个口径是,我们目前的开发是打算为 #249 铺路,如果你对 #249 还有什么建议或者方向调整也请提出,这样在本 Issue 开发前能有一个比较清晰的路径。

    对照 #249 的提案时意识到一个问题:单独实现该主动整理接口依然不能解决“AI为什么什么时候调用它”的问题,至少需要在 #249 中的能力说明完善后,通过注入形式教会 AI 使用。 所以对该 Feature 提出几个新建议:

    1. 接口实现后暂不对 agent 暴露,暂不进入工具列表
    
    2. 暂不对其单独实现 opt-in,默认不显示
    
    3. 具体的注入和对 agent 暴露的形式与 [[Feature] 注入形态:能力说明 + 指针 + 分时机注入(设计提案) #249](https://github.com/slow-stack/dsh-mneme/issues/249) 一同实现,本 Feature 仅实现功能本身并为后续开发铺路
    
  4. modusensus commented on Sep 19, 2026

    @modusensus
    MemberAuthor

    @heptaspirit #231 归你们,欢迎 —— 三条建议我这边都同意,补一句实施口径:

    关于 #249,先说结论:方向同意,P0 + P1 先做(能力说明进工具描述是纯加法零成本;分池是全篇证据最强的一条)。逐条给几条调整,都带核对过的依据:

    1. §10 的父开关默认关会翻掉现有默认

    src/config.js 现在是 autoInject: z.boolean().default(true)。如果新的「自动注入总闸」默认关,存量用户升级后会静默失去注入——这与仓库纪律的方向相反:那条纪律是「新功能不打扰老用户」,不是「把老功能关掉」。

    建议:autoInject 保留原语义与 default(true);分组只在面板呈现层做(把现有开关与新子项放进同一组),不引入第二个总闸。这样「父关 = 全不生效」的语义由既有的 autoInject 天然承担,验收里的「父关零注入」也随之成立。

    2. B2「摘要 + 指针」目前没有摘要可读

    service.toApiList 产出的 DTO 是 id / type / title / content / tags / importance / source / created_at / updated_at(外加条件展开的 scope 三键与 occurred_at/sensitivity)——没有摘要字段。现有注入本来就是「截断头(injectContentMaxChars,默认 300 字)+ id」。

    建议第一版把 B2 明确写成「截断头 + 显式 id 指针」:零新增字段、零新增 LLM 调用,与 §3 的非目标一致。真正的「摘要」留给 #230 / document 那条线,避免 B2 偷偷长出一条摘要管线。

    3. §7 那条「需确认追加 API 形态」已核实:可用

    宿主 dsh-agent 自己的实现就是范例(lib/index.js:160):agent/pre-step 回调签名是 async ({ agent, messages, signal, step }, next),await next() 拿到 decision,返回 { ...decision, messages: [...decision.messages, notice] } 即可追加;还支持 { prepend: true } 控制顺序。agent/turn-stopping、agent/request-error、agent/session-start 也都存在。

    所以 N3 的「写库 + 末尾注入」双落点不必先降级为持久规则,可以直接按主路径实现;「宿主不提供压缩前时机」的降级分支保留着即可(§4.4 的写法没问题)。

    4. 分池默认开:建议第一版仍默认关

    你的论证我认同(默认配置不该保留一个已实测的缺陷)。但本项目的行为变更纪律是「先能回滚、再谈默认」:建议第一版仍 opt-in 默认关,同时在 README 与面板文案里给出推荐配置(甚至一键套用),等真实负载数据到位再单独一次「翻默认」的提交。这样把「我们相信这个修正」与「我们替你改行为」分成两步。

    5. 面板分组不要挤进 P0/P1

    lib/client.js 是 4300+ 行手写、无构建步骤的文件,改它本身要单独排期。P0/P1 新增的配置键会自动出现在现有 flag 列表里(无需改面板即可用),所以建议:P0 → P1 → 面板分组 → P2 → P3(只计量),分组 UI 作为独立 PR。

    6. 一处分工确认

    P0(工具描述文案 + 库可见性行)与 P1(分池)我们这边可以直接接;你们手上是 #230 + #231。如果你们想连 P1 一起做(它动的是注入排序与轮换的既有契约,与你们 #230/#231 的注入形态同源),我们就不动,只在 review 配合——你们定。

  5. modusensus commented on Sep 19, 2026

    @modusensus
    MemberAuthor

    @heptaspirit 认领与三条口径(不进工具列表、不做独立 opt-in、注入形式与 #249 同批实现)都同意,按此开工。#249 的方向表态见那边的回复——第一批(能力说明 + constraint/preference 分池保真)与 #231 并行不冲突,你说的「为 #249 铺路」的路径成立。

  6. heptaspirit commented on Sep 21, 2026

    @heptaspirit
    Collaborator

    @modusensus

    CodeRabbit 在 #267 上把「报告判定与落库判定是两套实现」标成了 Major。这条我在 9-19 的 PR 评论(#267 comment)里已经交底过它的 A.1 与 A.3,当时问的是「放这个 PR 还是单独一个改动」。PR 评论你多半看不到,所以搬到这里问一句。

    分歧有三处,都在 src/organize.js 的 dryRun 与 src/service.js 的 saveWithDedupe 之间:

    1. 标题口径:dryRun 归一后比较(去空白与标点),写路径是 title.trim() 全等 → 标题只差标点时,报告 exact、apply 新建。
    2. 两条特殊键没实现:写路径对 type === "summary" && source === "dream" 按来源去重、对 narrative 按 tags[0] 去重;dryRun 只比标题 → 报告 new、apply 并入既有行并改写它的标题。
    3. 扫描窗口:dryRun limit: null(全量),写路径 limit: 100 → 同类型超过 100 行且重复行排在窗口外时,报告 exact、apply 新建。

    后果是报告会领先于写路径,而 agent 是照着报告的 match.id 去归档旧行的,库里可能留下一条没合并的新行。

    根因是 organize 自己重写了一遍写路径的匹配逻辑。收口办法是从 saveWithDedupe 里把「这个候选会并进哪一行」提成一个导出函数(写路径行为不变),dryRun 直接调它——这样这类分歧在结构上不会再出现。

    要不要做、放 #267 还是后续单独一个 PR,你说一句我照办。#267 那边我不再推代码,只加评论。

    另外补一句配套的:organize 现在只有 service 层入口(src/tools.js 的 10 个工具里没有它),这是照你定的口径留在「与 #249 同批暴露」。等下一步定了,把「注册工具 + 注入指引」接上,这套整理能力就能交给 AI 调用。在那之前只需要收口一处:dryRun 的 scope 默认值来自调用方 payload(src/organize.js:130),当它变成模型可调用的工具时,这个值应当改由 tools 层注入当前 scope——与 register_document 的 strictScope 同口径(src/document.js:87-88:调用者 scope 看不见的行按「不存在」处理,不得为跨 scope id 建立引用)。

  7. modusensus commented on Sep 21, 2026

    @modusensus
    MemberAuthor

    @heptaspirit 三处分歧我逐条对过 main 上的代码,全部属实,且根因正如你说的:organize 自己重写了一遍写路径的匹配逻辑。

    分歧 dryRun(organize.js) 写路径(saveWithDedupe) 后果
    标题口径 归一化(去空白/标点,小写) title.trim() 全等 报告 exact、apply 新建
    特殊键 无 summary+dream 按 source、narrative 按 tags[0] 报告 new、apply 并入并改写标题
    扫描窗口 limit: null 全量 limit: 100 窗口外重复:报告 exact、apply 新建

    报告领先于写路径的后果链也成立:agent 照报告的 match.id 归档旧行,库里留一条没合并的新行——这正是 CodeRabbit 那个 Major 的真实危害,不是纸面分歧。

    拍板:收口单独一个 PR,不进 #267。

    理由三条:

    1. feat(organize): agent 主动整理接口 dryRun -> apply(issue #231) #267 已过 @Anans-Ivresse 复审,你现在又承诺不再推代码——把核心写路径的重构塞进 feat(organize): agent 主动整理接口 dryRun -> apply(issue #231) #267,等于让复审作废重来,也违背你自己刚立的规矩;
    2. 按仓库纪律,动 service.js 核心路径要独立评审 + 带回归测试(AGENTS.md 自查清单 4);
    3. CodeRabbit 的 Major 由收口 PR 解决后,在 feat(organize): agent 主动整理接口 dryRun -> apply(issue #231) #267 合并说明里引用即可,不阻塞 feat(organize): agent 主动整理接口 dryRun -> apply(issue #231) #267 本身。

    收口办法本身赞成:从 saveWithDedupe 把「这个候选会并进哪一行」提成导出函数,dryRun 直接调它——匹配逻辑单一来源,这类分歧在结构上不会再出现,这是根治不是补丁。谁来写:你提的方案、你更熟 organize 侧,你来开,我们 review。

    scope 注入点(organize.js:130)——同意,记入工具化批次。 与 #249 同批暴露时,「dryRun 默认 scope 改由 tools 层注入当前 scope」与 register_document 的 strictScope 同口径(document.js:87-88:调用者 scope 看不见的行按「不存在」处理)。这条我认领进批次清单,不是现在改。

    关键判断:#231 拍板 = 收口单独 PR 由你开(不进 #267),scope 注入点随工具化批次处理;三处分歧核实全部属实。

  8. added a commit that references this issue on Sep 21, 2026
  9. heptaspirit commented on Sep 24, 2026

    @heptaspirit
    Collaborator

    @modusensus

    补一条关于「整理什么时候才会发生」的机制观察,是照这个 issue 的形态在自己库上跑了一遍之后落的结论。

    现状:整理是拉取式,长碎片那条路是推送式。 organize 的 dryRun 要吃调用方给的候选(src/organize.js 的 payload.candidates,一次上限 50 条),apply 按 agent 的判断落库,整条链要有人主动来调。而长碎片那条路是自动的:会话蒸馏逐条走 saveWithDedupe,去重键是 (type, title, agent_scope, workspace_scope, sensitivity),标题不同就新建。两边一比,结果就是同一个议题在库里长成几十条各自独立的事实,而库里没有任何一处会产生「这个话题该整理了」的信号。

    所以缺的是触发,不是动作。 我倾向触发信号走确定性遥测,动作留给 agent。

    为什么不放在 dream 做。 dream 是后台无人任务,适合判定可复算、结果可回滚的机械动作(精确重复、近重复对、撤销归档后的向量清理),它现在的候选阈值 dreamCandidateMinSim 也是这个用法。但把一族碎片收敛成一条时间线是判断活:哪条是旧口径、哪些矛盾是真的、某一步当时为什么这么定,这些因果只在工作上下文里。反过来看合并也说明同一件事:merge 能把几十条拍成一条,得到的是「一坨」而不是时间线,而且那一坨里会同时留着旧读数与新口径,读的人分不出哪个还算数。dream 在这里能做的是发信号,不是写。

    触发时机建议保守。 长段工作收尾、上下文接近压缩边缘,加上话题锚水位(同一话题新建到了 N 行)与归档净增速率。不要每轮都建议。阈值按 #254 定的「先记录不拦」同口径走:先只发信号只记数,等遥测有真实分布再定,别在没数据时拍一个数。

    实体层已经有一部分现成的整理素材,不用重新造。 抽取开着的时候,entity_attrs 本身就是一条按实体版本的属性时间线(valid_from / valid_until,当前值 = valid_until IS NULL),entity_relations 是边,两张表都带 memory_id。

    • 候选召回已经有一个不扫全表的锚:store.getLinkedMemoryIds(entityIds) 把实体 id 集合反查成 Map<memory_id, "attr" | "relation">(attr 档 = 当前有效属性行直挂的记忆,relation 档 = 关系行挂的记忆,同一记忆两条边都有时取 attr)。这跟给去重候选集定的「按结构化锚召回先收窄成本、不做全扫」是同一口径,dryRun 的候选可以由它推出来,不必只等调用方给。
    • 触发侧因此也多一个水位:实体的 mention_count、last_seen 与它的有效属性行数,本身就是「这个东西被反复写」的计数,不用新增埋点。

    但实体层现在与整理对不上,这一点得先说清。 entityRecall 会把归档行与遗忘行直接 continue 掉,而 registerDocument 全程不碰实体表(document.js 里没有 attrs / relations 的引用);对比 decisions.js 在合并记忆时会调 migrateAttrsToMemory,把 loser 的属性行迁到 keeper。也就是一次合并会把实体关联带走,一次升格吸收不会:被吸收进 document 的记忆归档之后,实体召回静默丢掉它们,属性行与关系行仍指着归档的记忆。整理要变成常规动作,这里需要一个口径,是吸收时把 entity_attrs.memory_id 迁到 document 行,还是保留指向归档行、只在检索面用 includeArchived 取。

    有一点我还没想清楚,想听你的。整理动作的产物(document 行)只在 documentMemoryEnabled 打开时才进注入(injectCandidates 的 INJECT_TYPES_WITH_DOCUMENT 门),而它吸收掉的 evidence 原子已经归档,也就是整理要成立得先确认这个开关是开的,否则一族会从注入面消失。这个耦合该在整理侧声明,还是该让 document 行无条件进注入、只受预算管?

  10. heptaspirit commented on Sep 24, 2026

    @heptaspirit
    Collaborator

    @modusensus

    承接上一条,把可执行的那半段补上,免得只停在「缺触发」。

    阶段一只落水位,不改行为。 把三类数放进状态面的同一处:话题锚的重复计数与间隔(G2 已在记)、实体侧的 mention_count / last_seen / 有效属性行数、归档侧的净增速率与可压掉行数(#275 第五指标)。这一阶段只记数、只在状态面显示,不启动整理;阈值等遥测有真实分布再定,与 #254 第一阶段同口径。

    阶段二让候选可由锚推出,动作仍留给 agent。 dryRun 的候选除调用方传入之外,允许按话题锚与 store.getLinkedMemoryIds 推;报告里带一族的版本序列(entity_attrs 的 valid_from / valid_until,当前值 = valid_until IS NULL)与 entity_relations 的边;apply 走 #230 的 document 与吸收归档。dream 那边不承担写 document,理由同上一条(它适合判定可复算、结果可回滚的机械动作,dreamCandidateMinSim 就是那个用法)。

    阈值那条想问一句。 近重复现在基本不开火:summarizeDedupeMinSim 0.92、searchSemanticDedupThreshold 0.95、dreamCandidateMinSim 0.85,都设在「几乎完全相同」那一侧,而一族碎片之间的关系常常不是重复,是同一件事的不同侧面、或不同时间的口径。是给同义改写留个口子,还是去重门保持现在的严口径、这类收敛一律归整理?我倾向前者不动:去重门管的是穿透,收敛管的是粒度,两件事别混在一个阈值里。

    这条不涉及。 不改去重键、不动拦截行为、不自动 VACUUM;物理删除与归档区的周期处理仍属 #275 第二批;整理也不采用「把多行合并成一行」那种追加式合并,得到的是一条越来越长的记录,不是可读的时间线。

  11. modusensus commented on Sep 24, 2026

    @modusensus
    MemberAuthor

    @heptaspirit 两件都拍板,另有一个 PR 侧的决定一并说。

    #254 穿透口:这批不加,等阶段二。 理由是阶段一没有拦截——豁免参数的消费者不存在,先立参数等于给没有的门装把手。而且你说的张力是真的:agent 自报「这是用户要求的」恰好是我们要在阶段二用确定性遥测去核的那个主张,现在把自报通道立起来,遥测就脏了。第二阶段接 confirm 时把这个口径(自报豁免在审计里怎么标、怎么跟水位对照)带着真实数据一起定。本批保持三条信号干净收口。

    #231 两阶段 + 去重门口径:都同意。

    1. 阶段一只落水位:三类数(G2 话题重复计数与间隔、实体侧 mention_count / last_seen / 属性行数、[Feature] 存储生命周期:归档区出口 + 审计表保留(容量治理议题) #275 第五指标)进状态面同一处,只记数不启动整理,阈值等真实分布——与 [Feature] 写入准入(non-write 判定):把「这条该不该进库」前移到 LLM 之前 #254 第一阶段完全同口径,不再重复论证。
    2. 阶段二候选由锚推出、动作留给 agent、dream 不写 document——同意你的分工:dream 只做判定可复算、结果可回滚的机械动作;整理是内容裁决,要在看得见水位的人手里。
    3. 去重门不动,收敛归整理:你这个「去重门管穿透、整理管粒度」的分界是对的,把两件事塞进一个阈值只会让 0.85–0.95 那几个数同时为两种语义负责。同义改写不留口子——它正是该由整理在阶段二处理的对象,水位里会以「同话题重复但内容哈希不同」的形状出现,恰好是 G2 与内容哈希(feat(admission): 内容哈希计量与归档行纳入去重候选集(#254) #311 的 dup 信号)两个信号的差集,不用新增判定面。

    合并侧:#311 已合,#312 update-branch 后跟着合,第五指标读的 content_hash 列已进 main。

  12. modusensus commented on Oct 20, 2026

    @modusensus
    MemberAuthor

    现状(2026-10-21 对照 main 实测)

    核心机制(dryRun → apply,全留审计)已经实现在 src/organize.js 里,但——它没有任何调用入口。

    已落地

    • dsh-mneme/src/organize.js(24,555 B)完整实现,模块头 :2-3 写明「agent 主动整理接口(issue [Feature] agent 主动整理接口:dryRun 比对报告 → agent 判断 → apply,全留审计 #231,口径基线 = 聊几个我觉得值得做的方向 #164 的『第三腿触发』):dryRun 出比对报告 → agent 判断 → apply 落库;筛除项进归档不删;调用全程留审计回执」。
    • 入口形态正是 issue 倾向的一个工具带 mode 参数:organize({ mode: "dryRun"|"apply", ... }, opts)(organize.js:63)。
    • dryRun 不写记忆表、只写一行 dream_runs 审计(run_type='organize',:196);apply 必须引用一次真实 dryRun 的 run_id,且只重放校验过的快照(:254-264、5284428)。
    • 依赖注入接线在 service.js:2557 const { organize } = createOrganizer({...}),:2573 挂到 service 上。
    • 测试:test/organize.test.js 存在。

    真正缺的:出口

    • dsh-mneme/src/tools.js 注册的十个工具是 memory_save / memory_search / memory_list / memory_get / memory_update / memory_delete / memory_forget / memory_archive / memory_register_document / memory_runtime —— 没有 organize(tools.js:127/207/266/314/352/411/433/468/581/689)。
    • api.js 里 organize 0 命中;api-standalone.js 里 0 命中。
    • 全仓 .organize( 调用点 0 处。

    也就是说:引擎造好了,油门没接上。 issue 要求的「新工具一个(dryRun/apply 两档)或一个工具带 mode 参数」正好就是缺的那一步。

    issue 里另外两条边界

    • 「opt-in 默认关」:因为没有出口,配置键也没注册(config.js 里 organizeEnabled 0 命中)。
    • 「settings.js 白名单 + lightMode 考虑」:同上,未做。

    还缺什么

    1. 在 tools.js 注册一个 memory_organize 工具(mode: dryRun|apply),description 写明「apply 必须引用真实 dryRun 的 run_id」;
    2. 配置键 organize.enabled(默认 false)+ settings.js 白名单成对注册 + 旗标计数锁;
    3. lightMode 下的行为定义(建议直接不出这个工具,而不是出但报错);
    4. 一条端到端用例:memory_organize(dryRun) → 拿到 run_id → memory_organize(apply, run_id) → 断言落库 + dream_runs 有回执。

    接手建议

    这是整个 open issue 里最「差一步」的一条:24 KB 的实现、测试都在,只差一个工具注册 + 一个配置键。工作量大概半天,而且不碰任何既有语义(默认关 = 零行为变化)。

  13. modusensus commented on Oct 21, 2026

    @modusensus
    MemberAuthor

    认领 #231 剩下的那一步:给已造好的引擎接上调用入口。src/organize.js 本体一行不动(四条硬规则、快照重放、回执语义全保持),只补工具注册 + 配置面 + 测试。

    计划

    1. 配置键:新增顶层扁平布尔 organizeEnabled,默认 false。
      • 进 settings.js 的 FEATURE_FLAG_BOOLEANS;扁平键,因此不需要 api.js 的 NESTED_FLAG_PATHS 条目([Feature] 写入准入(non-write 判定):把「这条该不该进库」前移到 LLM 之前 #254 那类漏登记坑在这里不适用);
      • 进 LIGHT_MODE_OFF:轻量档默认关,用户显式勾选仍然赢(合并顺序「用户开关 > 轻量预设 > bundle」,同 continuityRescueEnabled / distillRecords.enabled 口径)。理由:dryRun→apply 是两步协议,弱模型漏 run_id 或跳过报告的概率高,而一次 dryRun 的代价是嵌入调用 + 有界全库扫描。这比我先前设想的「轻量档直接不出这个工具」更准——预设关,但不剥夺用户显式开启的权利。
    2. 工具:tools.js 注册 memory_organize(mode: dryRun|apply,缺省 dryRun),复用既有 hiddenTools 闸门:organizeEnabled !== true 时不注册(模型看不到就不会调,比在描述里劝它少调用可靠——v0.8.5 工具暴露闸的原话定性)。描述按 set_scope_state([feature] 作用域级可逆闸门:让单路记忆能被单独按下与松开,而不必改配置重启 #471)的写法:写清「apply 必须引用一次真实 dryRun 的 run_id」「筛除=归档不删」「开关默认关,没开时工具根本不存在」。
    3. 可信上下文 opts.hiddenIds:organize.js:251 收 { hiddenIds },但全仓零生产者(工具没接,自然没人传)。工具侧按调用者 scope 计算:报告里被引用的行过 service.isVisibleInScope,看不见的按「不存在」处理——不让整理接口成为第二条跨 scope 改库的通道。
    4. 测试:tools.test.js 四条名单锁(默认关不含新工具)+ 新增「默认关不注册 / 开启后注册」两例;[Test] 全工具矩阵的「DTO 键集 ⊆ output schema」系统性断言——防 #184 类漂移 #195 矩阵 TOOL_NAMES 补 memory_organize 与 dryRun/apply 分支;端到端一例 dryRun → run_id → apply 断言落库且 dream_runs 有回执;api.test.js 旗标锁 103→104;docs-config-keys 守卫要求 docs/CONFIGURATION.md 补 organizeEnabled(含 lightMode 清单那一节)。
    5. README 的 v0.8 那格把「agent-driven organize」算作已发布能力,本条做完那句话才成立;CHANGELOG 条目进 [Unreleased]。

    本 PR 不做(各归各处)

    验收:npm run sync + npm test 全绿,check-sync 平价。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions