记录关键决策与理由(STA-11)。新决策先在此登记,再实现。
- 决定:agent-discipline 为独立仓库(本地文件夹名 dsh-methodology)的 out-of-tree bundle(
dsh plugin add安装)。 - 理由:脱离 harness 预发布期(0.1.1-rc.2)的仓库抖动与 private workspace 约束;独立版本与发布节奏;harness 官方支持外部 bundle(app-boot 的 resolveBundleDir + 扁平 node_modules 愈合)。
- 决定:MVP = persona 提示段 + agent_discipline_init + agent_discipline_audit;暂不做 ctx.features 事件溯源服务。
- 理由:形态 B 半天-1 天可落地并先跑 S1-S3 对比实验(§11.2);形态 C 需 1-2 周且应在有真实任务数据后再决策。
- 决定:与 harness 仓库约定一致(
.tsin local relative imports,tsconfig 开启 rewriteRelativeImportExtensions)。 - 理由:TS 5.7+ 支持发射时改写为 .js;保持 ESM + NodeNext 可运行。
- 决定:src/audit/core.ts 与 src/init/templates.ts 零 harness 依赖;dsh 依赖只出现在 src/tools 与 src/index.ts。
- 理由:单测可在未链接 harness 依赖时运行(ARCH-02 固化)。
- 决定:agent_discipline_init/audit 对预期失败(目录不存在、文件不可读等)返回 { ok:false, error },不抛异常。
- 理由:对齐 harness 工具结果契约(预期失败 resolve、基础设施失败 reject)。
- 决定:peer/dev 依赖用宽松预发布兼容范围,首次 REAL-composition 装载(F-006)时锁定实际版本。
- 理由:harness 处于开发者预览期,版本可能漂移。
- 决定:src/constants.ts 为代码面唯一出处;naming-registry.md 为仓库工件清单的注册视图;feature_list.json 的 status_legend 引用同一词表。
- 理由:方法论 §14.1 防词汇漂移;ARCH-03 固化。
- 决定:devDependencies 用
link:../deepseek-harness/...链接 harness 包,并在 package.json 的pnpm.autoInstallPeers=false禁用 peer 自动拉取。 - 理由(实测):
file:会触发 pnpm 解析被链接包自身的依赖,vendored cordis 的 manifest 用workspace:^声明 cosmokit——在插件仓库这个"外来 workspace"里无法解析,报 ERR_PNPM_WORKSPACE_PKG_NOT_FOUND;link:是纯符号链接,不解析被链接包依赖,传递类型沿 realpath 走进 harness 检出自己的 node_modules 解析。
- 决定:工具声明
output: { schema, render },对象 schema 必须带additionalProperties: false。 - 理由(实测):无 output 时 execute 返回类型推断为 Promise;对象 schema 缺 additionalProperties 时类型不匹配。签名对照 harness
packages/core/tools/src/schema.ts(DefineToolOptions<S,O>)。
- 决定:cordis.patch.yml 为顶层 YAML 数组(
cordis-plugin-include的 PatchOptions[]);新增插件行用- insert: [ {id, name}, ... ](无 id 的 insert 会 push 进顶层条目列表)。 - 理由(实测):in-repo bundle 用顶层
insert:对象,但 out-of-tree 的dsh plugin addoverlay 由 app-boot 的 parsePatchList 解析,要求顶层数组;无 insert 的行是 id 定向覆盖(目标不存在 → per-entry warning)。
- 决定:补丁行用
name: <包名或子路径>标识模块。 - 理由(实测):loader 的 EntryOptions.name 是模块 specifier;写
plugin:时该字段为 undefined,boot 报Cannot read properties of undefined (reading 'startsWith')。
- 决定:bundle 默认只插入主插件行;
agent-discipline/invariant伴生保留导出但不进 bundle(要用的组合须同时装载@deepseek-ai/dsh-invariants)。 - 理由(实测):
inject: ['invariants']让该行在无 invariants 服务的组合(产品 bundle,如 dsh-base)里永久 pending,app-boot 的 assertEntriesActivated fail-loud 拒绝启动;invariants 仅开发/测试组合装载。
- 决定:npm 包名、插件 name、bundle 行 id、提示段名、工具名(agent_discipline_init/audit)、invariant 名统一为 agent-discipline;本地文件夹名与仓库目录保持 dsh-methodology 不变(远端 GitHub 仓库名独立于文件夹名)。
- 理由:插件名"methodology"不像产品名,且"dsh"前缀暗示专属 DeepSeek Harness(方法论源自开源课程,见 README 致谢);agent-discipline 描述"给编码 Agent 的仓库工作加纪律"的实际职责。
- 决定:C5 锁文件候选扩展至 go.sum / Gradle(gradle.lockfile、dependencies.lock)/ Maven(pom.xml 版本钉定视作等价)/ pipenv(Pipfile.lock)/ uv / composer / Gemfile / packages.lock.json / bun;C6 增加 CI 配置证据(.github/workflows 目录枚举、.gitlab-ci.yml、.circleci、azure-pipelines、Jenkinsfile)与常见验证命令别名表(make verify、mvn verify、gradle check、go test ./...、cargo test、pytest 等)。
- 理由(实测):原实现只认 pnpm/package-lock/yarn/Cargo/poetry 与 scripts/check.sh|check.sh|Makefile,CI-only 与 Go/Java 仓库会误报失败;增强后显著改善对已有项目(brownfield)的适用性。检查仍为启发式,不执行目标仓库的验证命令本身。
- 决定:先落 tests/composition.spec.ts——vitest 内
new Context()+ctx.plugin(SystemPrompt, { includeRuntimeContext: false })+ctx.plugin(ToolRuntime)+ctx.plugin(agent-discipline 命名空间),断言:① assemble() 后提示段位于 harness:identity(-100) 与 deployment:persona(0) 之间、文本含「会话生命周期」与工具名;② tools 注册表恰好含 agent_discipline_init/audit 两个工具;真实 Loader 解析 patch 行的完整版留作后续。 - 理由:务实版覆盖"注册逻辑"全部断言面,只依赖两个服务的公开接口(section/assemble/get/schemas),稳、快、进
npm run check常驻(L2);完整版额外覆盖"Loader 补丁解析"层,目前有 CLI boot +--dump-config证据兜底,且依赖 harness 内部 API(Loader 导出名/app-boot 用法)更脆弱,放后续增强。 - 后续(已落地):完整版 = 复用
@deepseek-ai/dsh-app-boot的boot()(与 dsh 二进制同一启动路径)+loadOverlayPatches(解析本包 cordis.patch.yml)+ fixture cordis.yml +node_modules/agent-disciplinejunction(复刻 plugin add 的链接);见 tests/loader-composition.spec.ts,vitest 19/19 全绿,D-010/D-011/D-012 从"实测记录"升级为机器断言。
- 决定:四轮实验(R1/R2/R3 + S2/S3,见 docs/experiments/s1-s3.md)数据支撑上形态 C。C-1:
ctx.features服务(create/update/transition/verify + CAS 修订号)+ 严格 fold(WIP=1 违约、passing 无证据、非法状态跳转 → corrupt)+ 工具 get_feature/update_feature/verify_feature(替代手改 JSON);C-2:真实 invariant(会话增量折叠 + internal/dispatch 预提交拦截);C-3(可选):GUI 看板/投影单元,C-1/C-2 稳定后再定。状态真源取方案甲:会话日志为真源 + feature_list.json 作导出投影(README 演进路径推荐;贴合 OBS-04「模型可见⟺可日志化」)。状态词表不变(not_started/in_progress/blocked/passing,§3.3)。 - 理由(实验证据):R3 实证形态 B 验证门可被绕过(T3 的 B 会话未执行验证门、T1/T2 的 A 带病交付——模型自觉性波动 ≥ 纪律约束力),人工介入 A 3 次 vs B 1 次;S2 工件定位价值成立(B 4/4 vs A FAIL)。形态 C 买的是机器强制、不可绕过与防 AP-09/AP-10 的结构性保障,不是完成率(R3-T4 自觉会话无增量,如实记录)。分期理由:C-1 独立交付独立验证,不背完整事件溯源复杂度;C-2 的 dispatch 拦截点与 C-3 的投影都依赖 C-1 的服务先立住。
- 决定:C-1 真源从 D-016 的「harness 会话日志」修订为「插件自管事件日志」(
.harness/features/events.jsonl,append-only)+ feature_list.json 导出投影。 - 理由(实测边界缺口):harness 的 live
session.append不支持 ignorable 标记(opts 仅对 surface 事件开放);KNOWN_SESSION_EVENT_TYPES是 harness 构建时生成的枚举,注释明说 out-of-repo 插件事件 by construction 不在其中、且 event-name registration 被拒绝——feature/change 若不标 ignorable,跨会话重启时 harness 持久化读路径会拒绝解释整个日志(coordinator.ts "refusing to interpret the log")。自管事件日志完全规避该边界:事件溯源精神保留(真源 + 投影)、零 harness 改动、CAS 天然(append-only 日志单调)、机器强制更强(手改 feature_list.json 只是改投影,被真源折叠覆盖)。改 harness(session.append 加 ignorable)留作可选后续(C-2/C-3 若需与 harness 会话日志集成再评估)。
- 决定:C-3(F-011)拆两层——C-3a 实施:投影单元固化(FeatureService.exportProjection:显式导出 feature_list.json + 一致性校验,复用 checkFeatureLogHealth)与模型可见状态视图(feature_dashboard 工具:折叠汇总输出,含各状态计数/WIP 占用/证据摘要/投影一致性,符合 OBS-04「模型可见⟺可日志化」);C-3b 暂缓:GUI 特性看板。
- 理由:GUI 看板技术上可行(harness 有完整
dsh.client双面插件机制:client-modules 扫描声明dsh.client的包 → 组装 client entry graph → 注入window.__DSH_BOOT__;ui-goal 是蓝本——React 组件 +ctx.slots.inject/register槽位 + locale + client 构建链 tsdown.client),但成本 = React 客户端工程 + client 构建链 + 我们的 bundle 引入前端依赖 + web profile 集成测试,且与形态 C 核心价值(机器强制、不可绕过)正交——看板只增强"可见性"。模型可见状态视图以零成本覆盖同一信息需求;GUI 看板待形态 C 整体稳定后单独评估。
- 决定:
FeatureService.importExisting()把存量 feature_list.json(事件日志为空时)逐条导入为事件(not_started→create;in_progress/blocked→create+transition;passing→create+transition+verify,evidence 数组 join);update_feature工具加importExisting操作;工具输出out()剔除 undefined 字段(修复实测的 "value is not lossless JSON")。 - 理由(ScanApp 实测):形态 C 接管存量仓库时,投影重建会覆盖手写的 feature_list.json 且丢失未登记条目(实测中 F-001..F-003 曾从投影消失,git checkout 恢复后经迁移重入事件日志——7 行事件);工具输出含 undefined 字段(blockedReason 等)在 harness output 规范化下非 lossless JSON。
- harness 环境问题(记录,非本插件缺陷):0.1.2 的
dsh-plugin-package-inventory-deepseek在 headless 组合里 bare 包解析找不到 flat fallback($DSH_HOME/profiles/node_modules)→ REQUEST_EXTENSION;实测 overlay 以disabled: true绕过(不改 harness/用户 profile),建议上报 harness。
- 决定:root 解析从「构造时固化」改为「per-call 解析」——
FeatureService.resolveRoot(session?):显式 config.root → 会话session.header.cwd(sandboxPolicy.resolve({ session })的 per-session 路径)→ 构造回退根(静态 workspaceRoot → process.cwd());四个特性工具与 audit/init 工具的execute(args, exec)接收ToolRunContext,把exec.agent?.session传入;get_feature未找到返回{ feature: null }(oneOf object|null schema);agent_discipline_auditoutput schema 补全 scores/weakest/failedCritical(裁剪非承诺的 results,README「结构化字段」契约对齐)。 - 理由(问题报告,web GUI @ 3080 会话实测复现):web 多会话部署下
sandboxPolicy.workspaceRoot静态值 = harness 服务进程目录(config.workspaceRoot ?? process.cwd()),只有 per-session 解析才用session.header.cwd;原实现构造时固化静态根 → 工具读不到 ScanApp 状态(get_feature 空列表、importExisting 误报「feature_list.json 不可读」、audit 无 target 审计错目录且 schema 缺字段触发 lossless 拒绝)。ScanApp 历史事件由 headless CLI 会话写入(当时 process.cwd() 恰好 = ScanApp)——web 复现失败,恰证 per-session 缺失。init 工具同根因一并修复(D-020 范围扩展)。 - 附带:本仓库自身触发形态 B→C 迁移(session-handoff 触发条件①:本仓库真实会话第一次用 feature 工具登记前 importExisting)——12 条存量全部导入事件日志(32 行),投影重建含全部条目 + F-013;本仓库自证审计通过(audit 无 target 按会话 root 100/100)。