Skip to content

[fix] 新立项 change 可用专属 workspace:brief 随 worktree 迁移,回收前搬回主树 #52

Closed

Description

@stack-wuh

动机

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 出现两份可各自演进的受管状态(状态分裂),比报错更危险。

实证两次,都发生在内容包侧:

  1. 20261010-refactor-domain-driven-workflow:worktree inspect 对 L 级变更给 recommendation:"create",但读实现(lib/domains/worktree.mjs 的 execute 只向主工作区回写 brief,新树从 base 派生)后确认不可执行,被迫在 brief 里记「执行期修订」降级为 inline 分支——工具的默认路径与流程规范互相矛盾,执行者只能违反规范之一。
  2. 同轮还暴露配套的脏判定缺陷: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 记明关系
  • shadow-docs/knowledge/brief-frontmatter-crlf.md
  • 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 尊重域提供的跨树定位器。
    1. 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 被重复创建)。
    2. cli.mjs:readBrief(mod, r, o)——域导出 locateBrief 时优先用它,否则退回 brief(r, name(o))。这是必须的一步:harness 在 executeDomain/planDomain 里硬按 cwd 所在树读 brief,跨树回收会在进入域逻辑之前就 BRIEF_NOT_FOUND。其余 13 个域不改(它们按归属树读写正是设计意图)。
    3. 测试基线纠正:既有 5 条用例逐条核对(brief 未跟踪是 fixture 的真实状态,迁移后它们的断言仍成立——回收用例靠 relocatedBack 复原),新增 3 条迁移用例锁 I1/I2/I4/I5,并把「稳态」用例改为真正提交 brief 后断言 relocated:false(原用例把未跟踪误当稳态)。
    4. 知识闭环:新增 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 commit brief:未选。提交是显式动作(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 迁移部分。
  • 理由: 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 跨树去重;harness readBrief 优先用域定位器;两条 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

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions