From 59d0cdff0e1b7232f43404d14ba5c5e4e0cd33fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B0=E6=B6=9B?= <550947002@qq.com> Date: Fri, 16 Oct 2026 15:58:46 +0800 Subject: [PATCH] =?UTF-8?q?[tech]=20feat:=20=E6=94=AF=E6=8C=81=E6=9C=AA?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BB=BB=E5=8A=A1=E8=B7=A8=E7=94=B5=E8=84=91?= =?UTF-8?q?=E7=BB=AD=E4=BD=9C=E5=B9=B6=E5=8F=91=E5=B8=83=20core=20v3.8.4?= =?UTF-8?q?=E3=80=81cli=20v3.6.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [里程碑] core v3.8.4、cli v3.6.7 [摘要] 允许进行中任务随代码同步以跨电脑续作,并同步初始化迁移、环境检查和帮助文档。 --- .Knowledge/index.md | 2 +- .Knowledge/manifest-routing.json | 2 +- ...30\346\233\264\350\277\275\350\270\252.md" | 4 ++ .Knowledge/topics/f2s-task.md | 8 +++- .Knowledge/topics/flow2spec-collaboration.md | 10 +++-- .gitignore | 3 +- docs/en/commands-reference.md | 2 +- docs/en/team-collaboration.md | 20 ++++----- ...75\344\273\244\350\257\264\346\230\216.md" | 2 +- ...42\351\230\237\345\215\217\344\275\234.md" | 24 +++++------ package-lock.json | 6 +-- packages/cli/cli.js | 2 +- packages/cli/package.json | 4 +- packages/core/lib/doctor.js | 11 +++-- packages/core/lib/init.js | 20 ++++++--- packages/core/package.json | 2 +- scripts/test-doctor.js | 13 +++++- scripts/test-init-gitignore.js | 42 +++++++++++++++++-- 18 files changed, 123 insertions(+), 54 deletions(-) diff --git a/.Knowledge/index.md b/.Knowledge/index.md index 4e4d4c8..55d85a9 100644 --- a/.Knowledge/index.md +++ b/.Knowledge/index.md @@ -27,7 +27,7 @@ | f2s-doc-routing | `.Knowledge/topics/f2s-stock-docs-vs-req-docs.md` | stock-docs / req-docs 目录分工 | stock:[目录边界说明](.Knowledge/stock-docs/<目录边界说明>.md)(可选) | | fallback-triage | `.Knowledge/topics/f2s-fallback-triage.md` | 未命中或低置信度:分诊与澄清 | stock:[路由分诊说明](.Knowledge/stock-docs/<分诊说明>.md)(可选) | | config-precheck | `.Knowledge/topics/f2s-config-precheck.md` | 执行 `f2s-*` 前读 `flow2spec.config.json` / 编排开关(含 `intentRecognition` 意图识别)/ 更新检测 | Codex 长文:仓库根 `.codex/topics/f2s-config-check.md`;Claude/Cursor/Codex hooks:[路由摘要](topics/f2s-config-precheck.md) | -| f2s-task | `.Knowledge/topics/f2s-task.md` | 变更追踪、`.task/` 任务清单与跨会话续作 | stock:[任务清单与变更追踪](stock-docs/Flow2Spec-任务清单与变更追踪.md);长文:`rules/f2s-task.*` | +| f2s-task | `.Knowledge/topics/f2s-task.md` | 变更追踪、`.task/` 任务清单与跨会话/跨电脑续作 | stock:[任务清单与变更追踪](stock-docs/Flow2Spec-任务清单与变更追踪.md);长文:`rules/f2s-task.*` | | f2s-req-plan | `.Knowledge/topics/f2s-req-plan.md` | 需求/方案规划与实现;始终维护 `.task/` | 技能:`skills/f2s-req-plan/SKILL.md`;依赖 `f2s-task` | | f2s-git-commit | `.Knowledge/topics/f2s-git-commit.md` | 提交代码;默认检查知识库覆盖,快捷提交跳过覆盖检查 | 技能:`skills/f2s-git-commit/SKILL.md`;模板:[f2s-git-commit](../templates/skills/f2s-git-commit/SKILL.md) | | flow2spec-presentations | `.Knowledge/topics/f2s-flow2spec-presentations.md` | 本仓 Astro 宣传网站、文档导航与遗留 HTML 演示稿维护约定 | stock:[对外网站与演示](stock-docs/Flow2Spec-对外介绍演示.md) | diff --git a/.Knowledge/manifest-routing.json b/.Knowledge/manifest-routing.json index bcfa66a..1f92474 100644 --- a/.Knowledge/manifest-routing.json +++ b/.Knowledge/manifest-routing.json @@ -173,7 +173,7 @@ "topics": [ "flow2spec-collaboration" ], - "summary": "任务状态本地隔离、共享知识 delta 合入与 revision 冲突处理" + "summary": "任务按身份隔离与跨电脑续作、知识 delta 合入及 revision 冲突处理" }, { "task": "flow2spec-doctor", diff --git "a/.Knowledge/stock-docs/Flow2Spec-\344\273\273\345\212\241\346\270\205\345\215\225\344\270\216\345\217\230\346\233\264\350\277\275\350\270\252.md" "b/.Knowledge/stock-docs/Flow2Spec-\344\273\273\345\212\241\346\270\205\345\215\225\344\270\216\345\217\230\346\233\264\350\277\275\350\270\252.md" index cc3fdc7..854207e 100644 --- "a/.Knowledge/stock-docs/Flow2Spec-\344\273\273\345\212\241\346\270\205\345\215\225\344\270\216\345\217\230\346\233\264\350\277\275\350\270\252.md" +++ "b/.Knowledge/stock-docs/Flow2Spec-\344\273\273\345\212\241\346\270\205\345\215\225\344\270\216\345\217\230\346\233\264\350\277\275\350\270\252.md" @@ -48,6 +48,10 @@ - **归档**:完成后将 `active//` 整体移至 `completed/-/`,并从 `todo.json` 删除对应条目;若数组为空可删除 `todo.json`。 - **子任务与 Git worktree**:`subAgent=true` 或并行子任务若使用 **`git worktree`**(或 IDE 等价隔离目录),须在子任务结束或主 agent 合并后 **`git worktree remove`** 并 `git worktree list` 自检,禁止长期遗留孤儿 worktree;细则见配置根 **`rules/f2s-flow2spec-unified-entry.*`**。 +### Git 与跨电脑续作 + +`.gitignore` 使用 `.task/completed/` 与 `.task/*/completed/`,仅忽略两种布局的已完成归档。进行中的 `active/`、`todo.json` 与任务上下文可随代码提交、推送,在另一台电脑拉取后续作;两端应保持相同 `collaboration.developerId`(或相同的 Git 身份解析结果)。`init` 会将旧的整目录 `.task/` 忽略规则迁为上述两条规则。 + ### 3.1 `user-todos.md` 与 `acceptance.md`:用户侧两份文件,职责分离 | 文件 | 谁在做 | 内容焦点 | 写入时机 | diff --git a/.Knowledge/topics/f2s-task.md b/.Knowledge/topics/f2s-task.md index 1d4726e..bb1e1f3 100644 --- a/.Knowledge/topics/f2s-task.md +++ b/.Knowledge/topics/f2s-task.md @@ -1,6 +1,6 @@ --- id: f2s-task -revision: 0 +revision: 1 summary: "任务清单 .task 目录的创建、续作与归档规则" primary: policy confidence: manual @@ -49,6 +49,12 @@ TASK_ROOT/ ← `.task` 或 `.task/` 用户代办**必须**落在与 `task.md` 同目录的 **`user-todos.md`**;归档前**必须**生成与 `task.md` 同目录的 **`acceptance.md`**(验收清单),二者职责分离。细则见配置根 **`rules/f2s-task.*`**。 +## Git 与跨电脑续作 + +- `.gitignore` 使用 `.task/completed/` 与 `.task/*/completed/`,忽略单人及多人布局的已完成归档。 +- `active/`、`todo.json` 与任务上下文可提交并推送;另一台电脑拉取后,使用相同 developerId 解析到原 TASK_ROOT 续作。 +- `init` 自动将旧的整目录 `.task/` 忽略规则迁为上述两条规则。 + ## 跨会话续作 新会话先解析 `TASK_ROOT`;若存在该根下 `todo.json`,将用户首条消息与**仅该文件**内 `keywords` 匹配: diff --git a/.Knowledge/topics/flow2spec-collaboration.md b/.Knowledge/topics/flow2spec-collaboration.md index 67cd6f4..f8aa31d 100644 --- a/.Knowledge/topics/flow2spec-collaboration.md +++ b/.Knowledge/topics/flow2spec-collaboration.md @@ -1,7 +1,7 @@ --- id: flow2spec-collaboration -revision: 0 -summary: "任务状态本地隔离、共享知识 delta 合入与 revision 冲突处理" +revision: 2 +summary: "任务按身份隔离与跨电脑续作、知识 delta 合入及 revision 冲突处理" dependsOn: [f2s-task] primary: feature confidence: manual @@ -11,11 +11,13 @@ tags: [policy] ## 适用场景 +执行前须先读依赖主题 `f2s-task`。 + 用于回答 Flow2Spec 多人共用知识库、developerId 任务隔离、kb-delta 合入、topic revision 冲突与团队进度可见性。 ## 协作边界 -- `.task//` 保存个人 checklist、会话上下文与用户代办,默认不进 Git;Agent 只读写当前 `TASK_ROOT`。 +- `.task//` 保存个人 checklist、会话上下文与用户代办;进行中任务可随代码提交,completed 归档由 `.task/completed/` 与 `.task/*/completed/` 忽略;Agent 只读写当前 `TASK_ROOT`。 - `.Knowledge/` 保存团队共享事实,随代码通过 Git / PR 评审。 - `collaboration.enabled` 只控制任务根是否按 developerId 分层,不关闭知识库协作。 @@ -28,7 +30,7 @@ tags: [policy] ## 团队观察面 -团队进度以 PR、commit、`.Knowledge/` diff、`req-docs/` 与项目里程碑为准。`.task/` 是本地过程状态,不作为团队看板。 +团队进度以 PR、commit、`.Knowledge/` diff、`req-docs/` 与项目里程碑为准。`.task/` 保存个人过程状态,可通过 Git 同步进行中任务以跨电脑续作;两端保持相同 developerId。 ## 下钻入口 diff --git a/.gitignore b/.gitignore index a865a3d..960ec3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .agents/ -.task/ +.task/completed/ +.task/*/completed/ .DS_Store docs/*.json .Knowledge/update-check.json diff --git a/docs/en/commands-reference.md b/docs/en/commands-reference.md index 3d8854a..c53b040 100644 --- a/docs/en/commands-reference.md +++ b/docs/en/commands-reference.md @@ -703,7 +703,7 @@ It checks: - whether `flow2spec.config.json`, root `AGENTS.md`, and `.Knowledge/manifest-routing.json` exist and are usable - whether detected `.codex`, `.claude`, and `.cursor` roots contain their required entry and hook files - the resolved `developerId`, source, and `TASK_ROOT`; falling back to the legacy `.task/` root produces a warning -- whether the root `.gitignore` ignores `.task/` +- whether the root `.gitignore` ignores `.task/completed/` and `.task/*/completed/` while allowing active tasks to be committed - strict knowledge graph health, topic revisions, and routing metadata drift Human-readable output marks each check with `[PASS]`, `[WARN]`, or `[FAIL]` and includes a repair suggestion when applicable. `--json` returns a stable `ok / package / cwd / summary / checks` structure for CI and scripts. diff --git a/docs/en/team-collaboration.md b/docs/en/team-collaboration.md index 9547b51..c3b16dd 100644 --- a/docs/en/team-collaboration.md +++ b/docs/en/team-collaboration.md @@ -2,9 +2,9 @@ # Team Collaboration -Flow2Spec does not put every piece of runtime state into Git. It separates two kinds of information: what one developer is currently doing stays local; knowledge that can affect team decisions enters the repository. +Flow2Spec isolates task state by developer. Active tasks can travel with the code through Git for continuation on another computer, while confirmed team knowledge lives in `.Knowledge/`. -- `.task/` is personal working state and is ignored by Git by default. +- `.task/` holds personal working state. Active tasks and `todo.json` can be committed; completed archives are ignored. - `.Knowledge/` is team knowledge and is reviewed, committed, and pulled with the code. `developerId`, `kb-delta.json`, and topic revisions make this boundary executable. @@ -15,7 +15,7 @@ Flow2Spec does not put every piece of runtime state into Git. It separates two k | Content | Typical path | Visibility | Collaboration model | | --- | --- | --- | --- | -| Personal task state | `.task//` | Current developer and local Agent | Resume locally; do not commit | +| Personal task state | `.task//` | Members who pull the relevant commits | Resume by developerId; archive completed tasks locally | | Project knowledge | `.Knowledge/` | Everyone who pulls the repository | Delta merge and Git review | | Implementation and formal docs | Source code and `docs/` | Everyone who pulls the repository | Branches, PRs, and commits | @@ -126,7 +126,7 @@ skill forms a knowledge change -> task is archived locally after its gates pass ``` -`plan` does not write files. `apply` plans again immediately before writing, so a stale preview cannot force an outdated change onto disk. The delta remains local with the task; the `.Knowledge/` diff is the shared result. +`plan` does not write files. `apply` plans again immediately before writing, so a stale preview cannot force an outdated change onto disk. The delta stays with the task and can be committed for continuation; the `.Knowledge/` diff holds the confirmed result. --- @@ -150,7 +150,7 @@ flow2spec kb build flow2spec kb check --strict ``` -After apply, inspect the real `.Knowledge/` diff and commit it with the implementation. `.task/` should not appear among staged files. +After apply, inspect the real `.Knowledge/` diff and commit it with the implementation. Include active tasks and `todo.json` when continuation on another computer is needed. --- @@ -182,7 +182,7 @@ Step 4 cannot be replaced by concatenating text. The conflict exists to force a - Give each developer a stable, readable `collaboration.developerId`, especially on shared machines. - Pull before starting; keep a knowledge change and its implementation in the same PR or in traceable commits. - Keep topics focused. Oversized topics enlarge both the routing surface and the revision conflict surface. -- Do not add `.task/` back to Git or use `todo.json` as a team board. +- Ignore `.task/completed/` and `.task/*/completed/`; active tasks can be committed, while `todo.json` remains a personal continuation index. - Prefer delta-producing `f2s-kb-*` workflows for topic changes. Direct edits bypass revision preflight. - Review the `.Knowledge/` diff before merging. “Mergeable” means structurally safe, not necessarily correct business wording. - Keep the team on one Flow2Spec version so CLI schemas and templates agree. @@ -191,7 +191,7 @@ Step 4 cannot be replaced by concatenating text. The conflict exists to force a ## 7. How managers see progress -Managers cannot see another developer's active `.task/` checklist, by design. Team progress should come from durable commitments and outputs: +Committed active checklists provide task context. Formal team progress should come from durable commitments and outputs: - PRs or draft PRs for current delivery and blockers; - commits and `.Knowledge/` diffs for actual capability or rule changes; @@ -199,19 +199,19 @@ Managers cannot see another developer's active `.task/` checklist, by design. Te - milestones for results spanning multiple commits; - the team's issue tracker, board, or status reports for owners and deadlines. -If the team needs shared work-in-progress state, integrate the existing issue tracker instead of synchronizing `.task/active/`, which may contain session context, tentative judgments, and user todos with a different lifecycle. +Use `.task/active/` for resumable working state and the existing issue tracker for owners, deadlines, and blockers. Completed archives stay local. Archiving a previously committed active task produces deletions that should travel with the next commit. --- ## 8. Common questions -### If `.task/` is local, why keep developerId? +### If `.task/` can travel through Git, why keep developerId? Shared workspaces, build machines, and devcontainers can host multiple identities. More importantly, `TASK_ROOT` gives the Agent an enforceable boundary for task continuation. ### Does switching computers restore active tasks? -No. `.task/` is local. Put confirmed handoff information in a PR, requirement document, or `.Knowledge/`. +Commit and push the active task and `todo.json`, then pull them on the other computer. Keep the same `collaboration.developerId` or Git identity resolution so the Agent resumes from the same `TASK_ROOT`. ### Does `collaboration.enabled: false` disable knowledge collaboration? diff --git "a/docs/\345\221\275\344\273\244\350\257\264\346\230\216.md" "b/docs/\345\221\275\344\273\244\350\257\264\346\230\216.md" index f6711fe..bc6a031 100644 --- "a/docs/\345\221\275\344\273\244\350\257\264\346\230\216.md" +++ "b/docs/\345\221\275\344\273\244\350\257\264\346\230\216.md" @@ -821,7 +821,7 @@ flow2spec doctor --json - `flow2spec.config.json`、根 `AGENTS.md` 与 `.Knowledge/manifest-routing.json` 是否存在且可用 - 已存在的 `.codex`、`.claude`、`.cursor` 配置根是否包含必要入口和 hooks - 实际解析出的 `developerId`、来源与 `TASK_ROOT`;无法识别身份而回退到 `.task/` 时给出警告 -- 根 `.gitignore` 是否忽略 `.task/` +- 根 `.gitignore` 是否仅忽略 `.task/completed/` 与 `.task/*/completed/`,保留进行中任务可提交 - 知识图、topic revision 和 routing 元数据是否通过严格校验 文本输出用 `[PASS]`、`[WARN]`、`[FAIL]` 标记每项,并在异常项后给出修复建议。`--json` 输出稳定的 `ok / package / cwd / summary / checks` 结构,便于 CI 或脚本消费。 diff --git "a/docs/\345\233\242\351\230\237\345\215\217\344\275\234.md" "b/docs/\345\233\242\351\230\237\345\215\217\344\275\234.md" index cbd5a6a..a4cdabb 100644 --- "a/docs/\345\233\242\351\230\237\345\215\217\344\275\234.md" +++ "b/docs/\345\233\242\351\230\237\345\215\217\344\275\234.md" @@ -1,10 +1,10 @@ # 团队协作 -Flow2Spec 的团队协作不是把所有运行状态都塞进 Git。它先把内容分成两类:个人正在做什么,留在本地;已经足以影响团队判断的知识,进入仓库。 +Flow2Spec 按开发者隔离个人任务现场;进行中的任务可随代码通过 Git 同步,便于换电脑续作。团队确认的知识统一保存在 `.Knowledge/`。 对应到目录就是一句话: -- `.task/` 是个人过程,默认不进 Git; +- `.task/` 保存个人过程;`active/` 与 `todo.json` 可提交,completed 归档默认忽略; - `.Knowledge/` 是团队知识,和代码一起评审、提交、拉取。 这条边界比具体命令更重要。边界立住以后,`developerId`、`kb-delta.json` 和 topic revision 都只是让它可执行的机制。 @@ -15,11 +15,11 @@ Flow2Spec 的团队协作不是把所有运行状态都塞进 Git。它先把内 | 内容 | 典型路径 | 谁能看到 | 怎么协作 | | --- | --- | --- | --- | -| 个人任务现场 | `.task//` | 当前开发者和本机 Agent | 本地续作,不提交 | +| 个人任务现场 | `.task//` | 拉取了相关提交的成员 | 按 developerId 续作,completed 本地归档 | | 项目知识 | `.Knowledge/` | 拉取了仓库的所有成员 | delta 合入、Git 评审 | | 实现与正式文档 | 业务代码、`docs/` | 拉取了仓库的所有成员 | 常规分支、PR、commit | -`.task/` 里可以有尚未验证的判断、执行到一半的 checklist、需要用户处理的代办。它们对当前会话有用,却不等于团队事实。把这类文件推给所有人,得到的通常不是透明度,而是一批没人敢删、也没人知道是否过期的运行残留。 +`.task/` 里可以有尚未验证的判断、执行到一半的 checklist、需要用户处理的代办。它们对当前会话有用,却不等于团队事实。进行中任务通过 Git 同步后,Agent 仍只读写当前 `TASK_ROOT`;已完成任务按归档规则收尾。 `.Knowledge/` 正好相反。Agent 会用它判断业务规则、实现边界和读取路径,因此团队成员必须看到同一个版本。知识可以暂时不完整,但不能按开发者各存一份。 @@ -166,7 +166,7 @@ git pull flow2spec kb status ``` -`kb status` 检查 routing 漂移、topic 健康度,以及 **当前 TASK_ROOT** 下 active 任务里的 delta。它看不到 Bob 本机的 `.task/`,也不应该看到。 +`kb status` 检查 routing 漂移、topic 健康度,以及 **当前 TASK_ROOT** 下 active 任务里的 delta。即使已拉取其他人的任务,它也只扫描当前身份的任务根。 ### 工作中 @@ -185,7 +185,7 @@ flow2spec kb check --strict ### 收工 -知识已 apply 后,先看 `.Knowledge/` 的真实 diff,再和实现一起提交。`.task/` 不在 staged files 里。 +知识已 apply 后,先看 `.Knowledge/` 的真实 diff,再和实现一起提交。需要跨电脑续作时,可一并提交进行中任务与 `todo.json`。 ```bash git diff -- .Knowledge @@ -228,7 +228,7 @@ revision 是磁盘乐观锁,不是远端服务上的全局锁。它不会隔 - 每个人配置稳定、可读的 `collaboration.developerId`,共享机器上尤其如此; - 开工先拉取,知识变更和对应实现放在同一个 PR 或一组能互相追溯的提交里; - topic 按单一职责拆分。topic 过大不仅影响路由,也会扩大 revision 冲突面; -- 不直接把 `.task/` 加回 Git,不用 `todo.json` 充当团队看板; +- `.gitignore` 使用 `.task/completed/` 与 `.task/*/completed/`;进行中任务可提交,`todo.json` 用于个人续作; - topic 正文尽量走 `f2s-kb-*` 形成 delta。直接手改可以被 Git 记录,却绕过了 revision 预检; - 合并前看 `.Knowledge/` diff,不只看 `kb plan` 的“mergeable”。结构可合并不代表业务表述一定正确; - 团队统一 Flow2Spec 版本。CLI schema 和模板能力不一致时,协作问题很难靠流程约定补救。 @@ -239,7 +239,7 @@ topic 已经大到频繁互相阻塞时,先判断它是否混进了多个独 ## 7. 管理者怎么看进度 -`.task/` 不进 Git,manager 自然看不到每个人的 active checklist。这不是缺了一个同步功能,而是所有权边界:尚未验证的个人过程不该自动变成团队记录。 +已提交的 active checklist 可用于了解个人任务现场;正式团队进度以已确认的承诺与产出为准。 团队进度应从已经形成承诺或产出的地方看: @@ -249,21 +249,21 @@ topic 已经大到频繁互相阻塞时,先判断它是否混进了多个独 - 项目里程碑:跨多个提交的阶段性结果; - 团队原有的 issue、项目看板或日报:负责人、截止时间和阻塞项。 -如果团队确实需要共享“进行中”状态,优先接已有 issue 或看板,而不是同步 `.task/active/`。后者包含会话上下文、临时判断和用户代办,权限与生命周期都不同。 +`.task/active/` 用于保存可续作的过程状态;团队负责人、截止时间与阻塞项可继续由已有 issue 或看板管理。 -有人可能想把 `.task//completed/` 白名单放回 Git。这可以作为团队自定义方案,但不是 Flow2Spec 默认流程。采用前至少要定清楚保留周期、敏感信息清理、谁负责归档,以及它和正式里程碑的关系,否则很快会出现两套进度真值。 +`.task/completed/` 与 `.task/*/completed/` 默认保留在本地。归档此前已提交的 active 任务时,Git 会显示原路径删除;该删除随下一次提交同步。 --- ## 8. 常见问题 -### `.task/` 已经是本地目录,为什么还要 developerId? +### `.task/` 可以通过 Git 同步,为什么还要 developerId? 同一台构建机、devcontainer 或共享工作区可能有多个身份;更重要的是,Agent 需要一个明确的读取边界。`TASK_ROOT` 让“不要续作别人的任务”从提醒变成可检查的路径约束。 ### 换一台电脑后,原来的 active 任务会自动出现吗? -不会。`.task/` 默认不进 Git,它是本机过程状态。跨机器交接应把已确认内容写进 PR、需求文档或 `.Knowledge/`,不要依赖复制任务目录。 +将进行中任务和 `todo.json` 提交、推送,再在另一台电脑拉取即可。两端应配置相同 `collaboration.developerId`,或确保 Git 身份解析结果相同;Agent 随后按原 `TASK_ROOT` 续作。 ### `collaboration.enabled: false` 会关闭知识库协作吗? diff --git a/package-lock.json b/package-lock.json index a499e30..bbde640 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,10 +25,10 @@ }, "packages/cli": { "name": "@double-coding/flow2spec", - "version": "3.6.6", + "version": "3.6.7", "license": "ISC", "dependencies": { - "@double-coding/flow2spec-core": "^3.8.2" + "@double-coding/flow2spec-core": "^3.8.4" }, "bin": { "flow2spec": "cli.js" @@ -39,7 +39,7 @@ }, "packages/core": { "name": "@double-coding/flow2spec-core", - "version": "3.8.3", + "version": "3.8.4", "license": "ISC", "engines": { "node": ">=16" diff --git a/packages/cli/cli.js b/packages/cli/cli.js index fac1a46..dbf51cc 100755 --- a/packages/cli/cli.js +++ b/packages/cli/cli.js @@ -371,7 +371,7 @@ init 会: 传 --reset-knowledge 时才会强制用模板覆盖 .Knowledge 中模板承载部分。 4. 在各 agent 配置根写入对应的 rules、skills、入口和 hooks(若该客户端支持);具体落盘方式以客户端适配器为准。 5. 每次 init 将当前 locale 包模板 knowledge/index.md 复制到 .Knowledge/template/index.template.md,供 f2s-kb-upgrade 技能与 .Knowledge/index.md 对照;不自动改写 index.md。(「知识库升级」指 f2s-kb-upgrade 技能,init 本身不是升级命令。) - 6. 非破坏式补充 .gitignore:忽略 .task/ 与 .Knowledge/update-check.json 这类本地运行态。 + 6. 维护 .gitignore:仅忽略 .task/completed/、.task/*/completed/ 与 .Knowledge/update-check.json,进行中任务可提交。 7. 规则与技能在各 agent 配置根加载;其他模版类文件在 .Knowledge/template/ 等目录。 更多说明见 README.md 或 docs/使用说明.md diff --git a/packages/cli/package.json b/packages/cli/package.json index 4ada85c..5485bd7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@double-coding/flow2spec", - "version": "3.6.6", + "version": "3.6.7", "description": "在业务仓库初始化文档驱动、可写回知识库的 AI 协作骨架", "homepage": "https://github.com/double-coding-lab/Flow2Spec#readme", "repository": { @@ -19,7 +19,7 @@ "README.md" ], "dependencies": { - "@double-coding/flow2spec-core": "^3.8.2" + "@double-coding/flow2spec-core": "^3.8.4" }, "publishConfig": { "access": "public", diff --git a/packages/core/lib/doctor.js b/packages/core/lib/doctor.js index e269fd6..f0ed436 100644 --- a/packages/core/lib/doctor.js +++ b/packages/core/lib/doctor.js @@ -292,16 +292,19 @@ function runDoctor(cwd = process.cwd(), options = {}) { } } - const taskIgnored = isIgnoredByRootGitignore(cwd, ".task/"); + const taskIgnored = isIgnoredByRootGitignore(cwd, ".task/completed/") && + isIgnoredByRootGitignore(cwd, ".task/*/completed/") && + !isIgnoredByRootGitignore(cwd, ".task/") && + !isIgnoredByRootGitignore(cwd, "/.task/"); checks.push( taskIgnored - ? makeCheck("task-ignore", "任务目录", STATUS.pass, ".task/ 已在根 .gitignore 中忽略。") + ? makeCheck("task-ignore", "任务目录", STATUS.pass, "已忽略 completed 归档,进行中任务可提交。") : makeCheck( "task-ignore", "任务目录", STATUS.warning, - ".task/ 未在根 .gitignore 中忽略。", - "在根 .gitignore 中加入 .task/,或重新运行 flow2spec init。", + "任务忽略规则未对齐:应仅忽略 completed 归档。", + "将整目录 .task/ 规则替换为 .task/completed/ 与 .task/*/completed/,或重新运行 flow2spec init。", ), ); diff --git a/packages/core/lib/init.js b/packages/core/lib/init.js index 1016b67..daa7fa3 100644 --- a/packages/core/lib/init.js +++ b/packages/core/lib/init.js @@ -50,23 +50,31 @@ function removeKnowledgeUpdateCheckCache(cwd) { function ensureFlow2specGitignore(cwd) { const gitignorePath = path.join(cwd, ".gitignore"); - const required = [".task/", ".Knowledge/update-check.json"]; + const required = [".task/completed/", ".task/*/completed/", ".Knowledge/update-check.json"]; const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : ""; - const lines = existing.split(/\r?\n/).map((line) => line.trim()); + // Migrate the blanket ignore written by earlier init versions so active tasks + // and their index can travel with the repository between computers. + const retained = existing.split(/\r?\n/).filter((line) => + ![".task/", ".task", "/.task/", "/.task"].includes(line.trim()), + ).join(existing.includes("\r\n") ? "\r\n" : "\n"); + const lines = retained.split(/\r?\n/).map((line) => line.trim()); const missing = required.filter((item) => !lines.includes(item)); - if (missing.length === 0) { + if (missing.length === 0 && retained === existing) { return { path: gitignorePath, changed: false, added: [] }; } const additions = []; - if (!lines.includes("# Flow2Spec local state")) { + if (missing.length > 0 && !lines.includes("# Flow2Spec local state")) { additions.push("# Flow2Spec local state"); } additions.push(...missing); - const prefix = existing && !existing.endsWith("\n") ? `${existing}\n` : existing; + const prefix = retained && !retained.endsWith("\n") ? `${retained}\n` : retained; const separator = prefix && !prefix.endsWith("\n\n") ? "\n" : ""; - fs.writeFileSync(gitignorePath, `${prefix}${separator}${additions.join("\n")}\n`, "utf8"); + const next = additions.length > 0 + ? `${prefix}${separator}${additions.join("\n")}\n` + : retained; + fs.writeFileSync(gitignorePath, next, "utf8"); return { path: gitignorePath, changed: true, added: missing }; } diff --git a/packages/core/package.json b/packages/core/package.json index eef30c2..06698ff 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@double-coding/flow2spec-core", - "version": "3.8.3", + "version": "3.8.4", "templateVersion": "3.8.2", "description": "Flow2Spec Core APIs, knowledge engine, project initialization and shared resources", "homepage": "https://github.com/double-coding-lab/Flow2Spec#readme", diff --git a/scripts/test-doctor.js b/scripts/test-doctor.js index 994b8b1..56ddb84 100644 --- a/scripts/test-doctor.js +++ b/scripts/test-doctor.js @@ -24,7 +24,7 @@ function createHealthyProject(root) { write(path.join(root, ".Knowledge", "manifest-routing.json"), "{}\n"); write(path.join(root, ".codex", "AGENTS.md"), "See ../AGENTS.md\n"); write(path.join(root, ".codex", "hooks.json"), "{}\n"); - write(path.join(root, ".gitignore"), "node_modules/\n.task/\n"); + write(path.join(root, ".gitignore"), "node_modules/\n.task/completed/\n.task/*/completed/\n"); } const passKnowledge = () => ({ @@ -56,6 +56,17 @@ try { assert.match(formatDoctorReport(healthy), /\[PASS\] Node\.js/); assert.match(formatDoctorReport(healthy), /developerId=alice/); + for (const rules of [".task/\n", ".task/completed/\n", ".task/completed/\n.task/*/completed/\n/.task/\n"]) { + write(path.join(healthyRoot, ".gitignore"), rules); + const report = runDoctor(healthyRoot, { + nodeVersion: "v20.11.1", + knowledgeCheck: passKnowledge, + }); + const taskCheck = report.checks.find((check) => check.id === "task-ignore"); + assert.strictEqual(taskCheck.status, "warning"); + assert.match(taskCheck.repair, /completed/); + } + write( path.join(warningRoot, "flow2spec.config.json"), `${JSON.stringify({ collaboration: { enabled: true, developerId: "" } })}\n`, diff --git a/scripts/test-init-gitignore.js b/scripts/test-init-gitignore.js index ede2cae..882459b 100644 --- a/scripts/test-init-gitignore.js +++ b/scripts/test-init-gitignore.js @@ -2,6 +2,7 @@ const assert = require("assert"); const fs = require("fs"); const os = require("os"); const path = require("path"); +const { spawnSync } = require("child_process"); const runInit = require("../packages/core/lib/init"); const corePkg = require("../packages/core/package.json"); @@ -19,14 +20,16 @@ const corePkg = require("../packages/core/package.json"); }); assert.strictEqual(first.gitignoreResult.changed, true); assert.deepStrictEqual(first.gitignoreResult.added, [ - ".task/", + ".task/completed/", + ".task/*/completed/", ".Knowledge/update-check.json", ]); const afterFirst = fs.readFileSync(gitignorePath, "utf8"); assert(afterFirst.includes("node_modules/")); assert(afterFirst.includes("# Flow2Spec local state")); - assert(afterFirst.includes(".task/")); + assert(afterFirst.includes(".task/completed/")); + assert(afterFirst.includes(".task/*/completed/")); assert(afterFirst.includes(".Knowledge/update-check.json")); for (const agentRoot of [".cursor", ".claude", ".codex"]) { const hook = fs.readFileSync( @@ -48,9 +51,9 @@ const corePkg = require("../packages/core/package.json"); assert.strictEqual(second.gitignoreResult.changed, false); const afterSecond = fs.readFileSync(gitignorePath, "utf8"); assert.strictEqual( - afterSecond.split(".task/").length - 1, + afterSecond.split(".task/completed/").length - 1, 1, - ".task/ should not be duplicated", + "completed ignore should not be duplicated", ); assert.strictEqual( afterSecond.split(".Knowledge/update-check.json").length - 1, @@ -58,6 +61,37 @@ const corePkg = require("../packages/core/package.json"); ".Knowledge/update-check.json should not be duplicated", ); + spawnSync("git", ["init", "--quiet", tmpRoot]); + const taskPaths = [ + [".task/todo.json", false], + [".task/active/example/task.md", false], + [".task/alice/todo.json", false], + [".task/alice/active/example/task.md", false], + [".task/completed/example/task.md", true], + [".task/alice/completed/example/task.md", true], + [".Knowledge/update-check.json", true], + ]; + const checkIgnores = () => { + for (const [file, ignored] of taskPaths) { + const result = spawnSync("git", ["check-ignore", "--no-index", "-q", file], { cwd: tmpRoot }); + assert.strictEqual(result.status, ignored ? 0 : 1, file); + } + }; + checkIgnores(); + + // Migrate old blanket rules, including when all narrow rules already exist. + for (const blanket of [".task/", "/.task/", ".task", "/.task"]) { + for (const base of ["node_modules/\n", afterSecond]) { + fs.writeFileSync(gitignorePath, `${base}${blanket}\n`); + const migrated = await runInit(tmpRoot, ["cursor"], { configValues: { locale: "zh-CN" } }); + assert.strictEqual(migrated.gitignoreResult.changed, true); + assert(fs.readFileSync(gitignorePath, "utf8").includes("node_modules/")); + checkIgnores(); + const stable = await runInit(tmpRoot, ["cursor"], { configValues: { locale: "zh-CN" } }); + assert.strictEqual(stable.gitignoreResult.changed, false); + } + } + console.log("init gitignore tests passed"); } finally { fs.rmSync(tmpRoot, { recursive: true, force: true });