Repository navigation
[fix] 新立项 change 可用专属 workspace:brief 随 worktree 迁移,回收前搬回主树 #52
Description
动机
worktree 域对刚立项的 change 不可用,而每个 change 都从「刚立项」开始。
worktree execute 用 git worktree add -b <type>/<name> <path> <base> 从基线派生工作树,而 change create 刚写的 brief 是未跟踪文件——新树里根本没有它。后果不是报错难看,是流程当场断线:task set / review / commit 全部 BRIEF_NOT_FOUND,唯一绕过办法是人工把 brief 拷进新树,于是同一 change 出现两份可各自演进的受管状态(状态分裂),比报错更危险。
实证两次,都发生在内容包侧:
20261010-refactor-domain-driven-workflow:worktree inspect对 L 级变更给recommendation:"create",但读实现(lib/domains/worktree.mjs的execute只向主工作区回写 brief,新树从base派生)后确认不可执行,被迫在 brief 里记「执行期修订」降级为 inline 分支——工具的默认路径与流程规范互相矛盾,执行者只能违反规范之一。- 同轮还暴露配套的脏判定缺陷:brief 迁进专属 workspace 后是未跟踪文件,
worktree remove会因WORKTREE_DIRTY永远拒绝回收,唯一真相要么随目录被删、要么永远搬不回来。
change amend(实现期扩面登记)已由 #50 交付,本 change 只补另一半:brief 在 workspace 之间的归属迁移。现有 5 条 worktree 用例的 fixture 里 brief 全部是未跟踪状态却断言它留在主树——即测试前提把「缺陷行为」当成了契约,本 change 同时纠正这条测试基线。
非目标: 不做 change amend(#50 已实现且带 reviewReset/--scope/幂等,本 change 完全让位,废弃重复实现);不 bump 版本(发版是独立 change,先例 3081206 chore(release): v1.5.0 发版准备;且 change.amend 进入内容包 requiresCommands 必须等 v1.6.0 真的发布,见「跨仓顺序」);不做跨树双向同步或合并(那是状态分裂的开端);不改 archive 的 push 失败特例语义。
引用规范
shadow-docs/knowledge/plan-credential-chain.md(feat(change): 实现期扩面登记 change amend + 归档门旁路防护 #50 已更新)- 当前结论:
planHash=sha256(canon({command,data:norm(planData)}));norm 剥离边界=plan 自身副作用(workflow.planHash/release/issuePlan/commit与repo.changedFiles/clean);脏行为门禁必须在planData内联执行。 - 适用 scope: 迁移与
relocated/relocatedBack不得进入哈希输入(否则 execute 端必漂移);dirtyBesidesBrief属行为门禁,留在 planData 内联
- 当前结论:
shadow-docs/knowledge/brief-state-commit.md(feat(change): 实现期扩面登记 change amend + 归档门旁路防护 #50 已更新)- 当前结论: 写 brief 的 execute 域在
write(b)后补briefStateCommit,无差异不造空 commit;archive push 失败的特例恢复要人工 push 一次,根因正是 harness 从旧路径读 brief 会BRIEF_NOT_FOUND。 - 适用 scope: 本 change 的
locateBrief/readBrief是该特例的通用解法方向,但不改 archive 语义,只在 brief 记明关系
- 当前结论: 写 brief 的 execute 域在
shadow-docs/knowledge/brief-frontmatter-crlf.md- 当前结论: frontmatter 行尾契约与 autocrlf 处理;解析失败必须响亮
BRIEF_FRONTMATTER_REQUIRED;feat(change): 实现期扩面登记 change amend + 归档门旁路防护 #50 补了 CRLF 回写 LF 的用例。 - 适用 scope: 迁移用文件级复制,不得改写行尾;迁移后新树内 brief 必须仍可解析
- 当前结论: frontmatter 行尾契约与 autocrlf 处理;解析失败必须响亮
shadow-docs/knowledge/cli-output-contract.md- 当前结论: stdout 恒单行 JSON 契约,人类层走 stderr;新增字段属 additive,须同步契约测试。
- 适用 scope:
worktree.execute→relocated,worktree.remove.execute→relocatedBack,二者进 help 摘要与用例断言
shadow-docs/knowledge/install-distribution.md· 内容包knowledge/distribution-capability-contract.md- 当前结论: 兼容判定用能力名;跨仓演进顺序=执行层先发布支持、内容包随后声明。
- 适用 scope: 本 change 不新增命令键,故两侧声明均不变;发版归独立 change
rules/behavior.md§6(内容包:不要删你不懂的东西)/ §15(不要用历史覆盖 active Knowledge)- 当前结论: 删除/替换存量前先查引用链搞清楚为什么存在。
- 适用 scope: 既有 5 条 worktree 用例的断言基线要逐条核对,不能整批改写掩盖真实回归
决策
- 选型: A. 迁移而非复制,归属树唯一,且 harness 尊重域提供的跨树定位器。
lib/domains/worktree.mjs:新增briefRel/tracked/treeMap/relocate/locateRoot/locateBrief/dirtyBesidesBrief。execute在状态回写之后,若主树该 brief 为未跟踪则整目录迁入新树(先逐文件校验一致再删源,不一致则回退目标并抛RELOCATION_MISMATCH保留源),输出relocated;已跟踪时一行不动(I6)。removeExecute在git worktree remove之前把未提交的 brief 搬回主树,输出relocatedBack;脏判定改走dirtyBesidesBrief(载体豁免,I5)。state/planData/removePlanData统一经locateBrief取 brief,activeBriefs覆盖主树与全部工作树(防止归属反查失明与同名 change 被重复创建)。cli.mjs:readBrief(mod, r, o)——域导出locateBrief时优先用它,否则退回brief(r, name(o))。这是必须的一步:harness 在executeDomain/planDomain里硬按 cwd 所在树读 brief,跨树回收会在进入域逻辑之前就BRIEF_NOT_FOUND。其余 13 个域不改(它们按归属树读写正是设计意图)。- 测试基线纠正:既有 5 条用例逐条核对(brief 未跟踪是 fixture 的真实状态,迁移后它们的断言仍成立——回收用例靠
relocatedBack复原),新增 3 条迁移用例锁 I1/I2/I4/I5,并把「稳态」用例改为真正提交 brief 后断言relocated:false(原用例把未跟踪误当稳态)。 - 知识闭环:新增
shadow-docs/knowledge/worktree-brief-relocation.md(唯一性/不丢数据/载体豁免三条执行约束)并加shadow-docs/menu.md路由;本仓首次登记shadow-docs/domain.md上下文地图(内容包已在 6.8.0 落地该规范,本仓补上,domain值域=cli-infrastructure/install-distribution/github-integration)。
- 对比方案:
- B. 复制到新树、主树留一份:未选。两份受管状态各自演进就是 divergence;
task set在 A 树、review在 B 树会产生两个真相,比报错难发现得多。 - C. 要求先提交 brief 再建 workspace:未选。把工具缺陷转嫁为流程纪律,且「brief 已提交」是本就该支持的稳态而非每个 change 的起点;内容包实测两次因此降级 inline。
- D.
worktree add后自动git commitbrief:未选。提交是显式动作(commit plan/execute带文件集与 planHash 凭证),自动提交会绕过凭证链语义。 - E. 复用我上一轮的
change amend简版:未选,主动废弃。feat(change): 实现期扩面登记 change amend + 归档门旁路防护 #50 的实现严格更强(reviewReset作废已 passed review、--scope同修、幂等短路、CRLF 回写、专用错误码AMEND_INPUT_REQUIRED、5 个用例 + 知识闭环)。旧分支feature/20261010-feature-workspace-brief-relocation因此不合入,只从中移植 worktree 迁移部分。
- B. 复制到新树、主树留一份:未选。两份受管状态各自演进就是 divergence;
- 理由: brief 的唯一性是既有不变量(
change create的CHANGE_EXISTS门与conflict.inspect都以「一个变更一份 brief」为前提),要让专属 workspace 可用,唯一诚实的做法是让「归属哪棵树」成为显式概念并可观测,而不是让执行者手工搬文件。
任务
Phase 1 — 迁移实现
- brief 迁入专属工作树 —
lib/domains/worktree.mjs—relocate(逐文件校验一致再删源,失败回退目标并抛RELOCATION_MISMATCH);execute在状态回写后按主树跟踪态决定是否迁移,输出relocated - brief 搬回与载体豁免 —
lib/domains/worktree.mjs—removeExecute删目录前搬回未提交 brief,输出relocatedBack;dirtyBesidesBrief排除 brief 自身,真实脏改动仍WORKTREE_DIRTY - 跨树定位与发现 —
lib/domains/worktree.mjs,cli.mjs,lib/commands.mjs—locateRoot/locateBrief覆盖主树与全部工作树,activeBriefs跨树去重;harnessreadBrief优先用域定位器;两条 worktree help 摘要与双语说明补迁移语义
Phase 2 — 契约测试与全量回归
- 三条迁移用例 —
test/cli.test.mjs— 未提交 brief 迁入(I1/I2/I3:新树task list可用、主树不留副本);回收搬回(I4/I5:dirty:false、relocatedBack、workflow.worktree=null);稳态不迁移(I6,先把 brief 真提交再断言relocated:false) - 不变量探针取证(口径已由用户裁定变更) —
shadow-docs/changes/20261010-fix-worktree-uncommitted-brief/brief.md— 2026-10-11 用户裁定「完全干掉 TDD,转向 DDD」,「全量测试套件必须绿」不再是合并门槛;本条改为对四条不变量取真实观察点:E2E 全链手工复现 + 分片测试作为可选契约探针,探针未绿项如实标注不作伪证
Phase 3 — 知识闭环与跨仓说明
- 迁移知识与菜单路由 —
shadow-docs/knowledge/worktree-brief-relocation.md,shadow-docs/menu.md— 唯一性/不丢数据/载体豁免三条执行约束 + 适用边界(跨树定位仅 worktree 域,其它域按归属树)+ 验证方式 - 本仓上下文地图 —
shadow-docs/domain.md— 登记cli-infrastructure/install-distribution/github-integration三上下文与上游workflow-governance的反腐边界,使卡片domain有值域可依 - 跨仓顺序与偏差记录 —
shadow-docs/changes/20261010-fix-worktree-uncommitted-brief/brief.md— 结果段记观察点与change amend让位说明;遗留段写明「change.amend进入内容包requiresCommands必须等 v1.6.0 发布并升级本机 CLI,否则ARTIFACT_INCOMPATIBLE判红」
完整 brief:shadow-docs/changes/20261010-fix-worktree-uncommitted-brief/brief.md
Activity