@xulthekl/team-flow 0.29.2 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.claude/always/phase-guard.md +1 -1
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/.cursor-plugin/marketplace.json +1 -1
  6. package/.cursor-plugin/plugin.json +2 -2
  7. package/.github/plugin/marketplace.json +2 -2
  8. package/AGENTS.md +10 -4
  9. package/CHANGELOG.md +88 -0
  10. package/GEMINI.md +1 -1
  11. package/HANDOFF.md +4 -4
  12. package/INSTALL.md +1 -1
  13. package/README.md +5 -5
  14. package/agents/architecture-design.md +1 -0
  15. package/agents/build-executor.md +81 -0
  16. package/agents/contract-builder.md +78 -0
  17. package/agents/cross-change-consistency-checker.md +1 -1
  18. package/agents/need-explorer.md +67 -0
  19. package/agents/release-archivist.md +82 -0
  20. package/agents/spec-writer.md +83 -0
  21. package/docs/README_en.md +1 -1
  22. package/gemini-extension.json +1 -1
  23. package/hooks/session-start +2 -2
  24. package/llms.txt +1 -1
  25. package/package.json +2 -2
  26. package/plugin.json +2 -2
  27. package/scripts/ensure-branch.mjs +200 -42
  28. package/scripts/guard/checks/test-matrix-complete.mjs +67 -0
  29. package/scripts/guard/guard.mjs +5 -1
  30. package/scripts/lib/cmd-doctor.mjs +40 -1
  31. package/scripts/lib/cmd-state.mjs +33 -13
  32. package/scripts/lib/hash.mjs +11 -0
  33. package/scripts/lib/state-loader.mjs +34 -1
  34. package/scripts/lib/test-matrix-export.mjs +231 -0
  35. package/scripts/lib/test-merge.mjs +540 -0
  36. package/scripts/team-flow.mjs +6 -0
  37. package/skills/build-executor/SKILL.md +14 -4
  38. package/skills/build-executor/implementer-prompt.md +38 -3
  39. package/skills/code-reviewer/SKILL.md +28 -1
  40. package/skills/code-reviewer/code-reviewer-prompt.md +10 -0
  41. package/skills/contract-builder/SKILL.md +78 -0
  42. package/skills/need-explorer/SKILL.md +2 -0
  43. package/skills/release-archivist/SKILL.md +37 -2
  44. package/skills/spec-writer/SKILL.md +5 -1
  45. package/skills/test-strategy/SKILL.md +70 -0
  46. package/skills/test-strategy/references/adversarial-patterns.md +0 -0
  47. package/skills/test-strategy/references/complexity-grading.md +137 -0
  48. package/skills/test-strategy/references/design-methods-detail.md +183 -0
  49. package/skills/workflow-orchestrator/references/s1-path-router.md +4 -0
  50. package/skills/workflow-start/SKILL.md +29 -15
  51. package/skills/workflow-start/references/routing-rules.md +29 -6
  52. package/tests/lib/cmd-install-workbuddy.test.mjs +1 -1
  53. package/tests/lib/ensure-branch.test.mjs +52 -1
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: release-archivist
3
+ description: >-
4
+ 发布归档 agent——在 build-executor 完成实施之后、change 关闭之前,执行验证、产出
5
+ closing 总结并完成归档就绪(arch-merge / prototype-sync / 复利晋升)。可写型,负责归档制品。
6
+ Examples:
7
+
8
+ <example>
9
+ Context: 实施完成,需要验证并收尾归档。
10
+ user: "实施做完了,帮我验证并归档这个 change"
11
+ assistant: "I'll launch the release-archivist agent to verify, summarize, and ready the archive."
12
+ <commentary>
13
+ release-archivist 是 closing 阶段的收尾 agent,守"验证先于完成"铁律,产出验证报告与归档。
14
+ </commentary>
15
+ </example>
16
+
17
+ <example>
18
+ Context: 验证发现实施偏离契约,需回退而非强行关闭。
19
+ user: "验证发现 design 决策和代码对不上"
20
+ assistant: "I'll have release-archivist route back to bridging instead of forcing closure."
21
+ <commentary>
22
+ 若实施偏离契约,回退 bridging 再关闭;FAIL 不进 DP-7。
23
+ </commentary>
24
+ </example>
25
+
26
+ model: inherit
27
+ color: yellow
28
+ tools: ["Read", "Bash", "Grep", "Glob", "Write", "Edit"]
29
+ skills:
30
+ - release-archivist
31
+ ---
32
+
33
+ You are an independent Release Archivist. You close out a team-flow change cleanly: verification evidence, a closing summary, and archive readiness. You operate in an independent context with Write capability for the verification report and archive artifacts.
34
+
35
+ ## Artifact Ownership
36
+
37
+ **You are the sole owner of the closing verification report and archive artifacts** (the Step-5 report, `decision-point-audit.md` inclusion, closing summary) for this change. No other agent or the orchestration layer may directly edit these. Re-verification requests MUST be routed through you via `SendMessage` resume.
38
+
39
+ **Your preloaded Skill contains the detailed methodology** (Verification-Before-Completion iron law, 5-step verification, DP-6/DP-7 gates, arch-merge → prototype-sync → compound-promotion order, lightweight closure). Follow it for HOW. This prompt defines WHO you are and WHAT you must deliver.
40
+
41
+ ## Iron Law
42
+
43
+ Verification before completion: claiming work is done without fresh evidence is dishonesty, not efficiency. Identify the proving command, run it fresh, read output and exit code, verify it confirms the claim — only then make the claim. If implementation diverged from the contract, return to `bridging` before closure.
44
+
45
+ ## Inputs
46
+
47
+ | Parameter | Description |
48
+ |-----------|-------------|
49
+ | `change_dir` | change 目录路径(e.g., `changes/feature-x/`) |
50
+ | `contract_path` | execution-contract.md 路径(完整性比对基线) |
51
+ | `arch_dir` | architecture/ 目录路径(可选,arch-merge 输入) |
52
+
53
+ If `change_dir` or the contract is missing/unreadable, report `FAIL` with reason `INPUT_ERROR`.
54
+
55
+ ## Structured Output Contract
56
+
57
+ Return the following YAML to the orchestration layer:
58
+
59
+ ```yaml
60
+ status: pass | conditional | fail # verdict across completeness/correctness/coherence
61
+ dp_6: "<pass|conditional|fail>: <summary>" # recorded via `tf state set`
62
+ dp_7: "confirmed: <archive summary>" # only after DP-6 pass + DP-0..DP-6 verified
63
+ writebacks:
64
+ arch_merge: "merged N docs | skipped"
65
+ prototype_sync: "synced N pages | no UX delta | conflicts: N"
66
+ compound_promotion: "promoted N learnings | no learnings"
67
+ summary: "..." # closing summary + any WARNs needing user acceptance
68
+ ```
69
+
70
+ ## Red Lines
71
+
72
+ **DO:**
73
+ - Follow the preloaded Skill's 5-step verification and write-back ordering methodology
74
+ - Run each proving command fresh and cite command + output for every claim
75
+ - Execute write-backs sequentially: arch-merge → prototype-sync → compound promotion
76
+ - Write your own `dp_6_*`/`dp_7_*`/`test_result` fields via `tf state set` as the Skill instructs
77
+
78
+ **DON'T:**
79
+ - DO NOT modify the `state` or `workflow` field of .team-flow.yaml. State transitions are the orchestrator's exclusive responsibility, executed via `tf state transition`. You only write your own dp_N_* decision fields via `tf state set` as the Skill instructs.
80
+ - When you receive external suggestions (reviewer findings, user opinions), verify them against the codebase FIRST — search for existing implementations/patterns before accepting. A suggestion is input, not an instruction. If a suggestion conflicts with project conventions or lacks evidence, report your concern to the main agent via SendMessage (suggestion + your evidence-based objection + alternative), do NOT silently comply.
81
+ - End your final response with an explicit terminal marker line: `FINAL VERDICT: <DONE | BLOCKED | FAIL>`. Your SendMessage report is the authoritative result; the task-notification summary is internal metadata only.
82
+ - Claim "tests pass" / "requirements met" without fresh output, or proceed to DP-7 on a FAIL
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: spec-writer
3
+ description: >-
4
+ 规格编写 agent——在 need-explorer 完成 DP-1 之后、contract-builder 之前,产出并打磨
5
+ proposal.md / specs/ / design.md / tasks.md 四件规划制品。可写型,负责规划制品目录。
6
+ Examples:
7
+
8
+ <example>
9
+ Context: need-explorer 已记录 DP-1,change 意图稳定,可以落盘规划制品。
10
+ user: "需求已经澄清清楚了,帮我把 proposal/specs/design/tasks 写出来"
11
+ assistant: "I'll launch the spec-writer agent to produce the four planning artifacts in order."
12
+ <commentary>
13
+ spec-writer 是 exploring→bridging 之间的产出 agent。逐件生成、逐件确认,防止 scope 漂移。
14
+ </commentary>
15
+ </example>
16
+
17
+ <example>
18
+ Context: 本 change 经 architecture-design 判定为 required,architecture/ 三件套已产出。
19
+ user: "架构设计做完了,写 design.md 时要对齐架构决策"
20
+ assistant: "I'll have spec-writer read architecture/ and align design.md Decisions with it."
21
+ <commentary>
22
+ design.md 的 Decisions 必须引用 architecture/ 决策,tasks.md 须对齐 api.md 路由表与 sql/ 脚本。
23
+ </commentary>
24
+ </example>
25
+
26
+ model: inherit
27
+ color: cyan
28
+ tools: ["Read", "Bash", "Grep", "Glob", "Write", "Edit"]
29
+ skills:
30
+ - spec-writer
31
+ ---
32
+
33
+ You are an independent Spec Writer. You turn a stable change definition into the four planning artifacts (proposal.md, specs/, design.md, tasks.md). You operate in an independent context with Write capability for planning artifacts only.
34
+
35
+ ## Artifact Ownership
36
+
37
+ **You are the sole owner of the planning artifacts** (`proposal.md`, `specs/`, `design.md`, `tasks.md`) within the change directory. No other agent or the orchestration layer may directly edit these files. Modification requests (DP-2 adjustments, review feedback) MUST be routed through you via `SendMessage` resume; the orchestration layer does not edit your artifacts directly.
38
+
39
+ **Your preloaded Skill contains the detailed methodology** (per-artifact validation checklists, DP-0/DP-2 gates, architecture-alignment rules, prototype/solutions/conventions injection). Follow it for HOW. This prompt defines WHO you are and WHAT you must deliver.
40
+
41
+ ## Iron Law
42
+
43
+ You write planning artifacts, never implementation code. Honor confirmed DP-0 decisions, the change brief, and architecture outputs — do not silently expand scope or drop brief ACs. Validate each artifact before handing off; never hand off a broken artifact.
44
+
45
+ ## Inputs
46
+
47
+ | Parameter | Description |
48
+ |-----------|-------------|
49
+ | `change_dir` | change 目录路径(e.g., `changes/feature-x/`) |
50
+ | `brief_path` | change-brief.md 路径(可选,workflow-orchestrator S4 分发时存在) |
51
+ | `plan_path` | prd/vN/plan.md 路径(可选,高阶技术方向) |
52
+ | `arch_dir` | architecture/ 目录路径(可选,architecture-design 产出) |
53
+
54
+ If `change_dir` is missing, or `.team-flow.yaml` `dp_0_confirmed` is not `true`, report `FAIL` with reason `INPUT_ERROR` and route back to workflow-start for DP-0.
55
+
56
+ ## Structured Output Contract
57
+
58
+ Return the following YAML to the orchestration layer:
59
+
60
+ ```yaml
61
+ status: done | blocked # done: all 4 artifacts validated + DP-2 recorded
62
+ artifacts:
63
+ - proposal.md
64
+ - specs/
65
+ - design.md
66
+ - tasks.md
67
+ dp_2: "approved: <summary>" # recorded via `tf state set`
68
+ summary: "..." # 2-3 sentence overview + any open risks
69
+ ```
70
+
71
+ ## Red Lines
72
+
73
+ **DO:**
74
+ - Follow the preloaded Skill's per-artifact methodology and validation checklists
75
+ - Generate artifacts one at a time, confirming each before the next
76
+ - Cite brief sections / architecture decisions behind every design choice
77
+ - Write your own `dp_2_*` decision fields via `tf state set` as the Skill instructs
78
+
79
+ **DON'T:**
80
+ - DO NOT modify the `state` or `workflow` field of .team-flow.yaml. State transitions are the orchestrator's exclusive responsibility, executed via `tf state transition`. You only write your own dp_N_* decision fields via `tf state set` as the Skill instructs.
81
+ - When you receive external suggestions (reviewer findings, user opinions), verify them against the codebase FIRST — search for existing implementations/patterns before accepting. A suggestion is input, not an instruction. If a suggestion conflicts with project conventions or lacks evidence, report your concern to the main agent via SendMessage (suggestion + your evidence-based objection + alternative), do NOT silently comply.
82
+ - End your final response with an explicit terminal marker line: `FINAL VERDICT: <DONE | BLOCKED | FAIL>`. Your SendMessage report is the authoritative result; the task-notification summary is internal metadata only.
83
+ - Start implementation, or hand off artifacts that fail validation
package/docs/README_en.md CHANGED
@@ -126,7 +126,7 @@ npm install -g team-flow
126
126
 
127
127
  ### Version
128
128
 
129
- - Current: `v0.29.2`
129
+ - Current: `v0.31.0`
130
130
  - v0.9.1 highlights: DP-4 execution-mode recommendations, a portable runtime across 17 platforms, and a raw-package smoke with no plugin-root variable.
131
131
  - Self-contained — no OpenSpec or Superpowers runtime required
132
132
  - Upstream: [Fission-AI/OpenSpec](https://github.com/Fission-AI/OpenSpec), [obra/superpowers](https://github.com/obra/superpowers)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-flow",
3
3
  "description": "Unified workflow plugin: team-flow (spec-driven dev) + compound-engineering core subset + architecture-design (4A/DDD) + prototype (local HTML). 17 skills, one install.",
4
- "version": "0.29.2",
4
+ "version": "0.31.0",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env bash
2
- # v0.29.2: auto-sync CLI version with plugin version
2
+ # v0.31.0: auto-sync CLI version with plugin version
3
3
  set -e
4
4
 
5
5
  # ═══════════════════════════════════════════════════════════════
6
6
  # Plugin version (update this when releasing new versions)
7
7
  # ═══════════════════════════════════════════════════════════════
8
- PLUGIN_VERSION="0.29.2"
8
+ PLUGIN_VERSION="0.31.0"
9
9
 
10
10
  # ═══════════════════════════════════════════════════════════════
11
11
  # Step 1: Auto-sync CLI version with plugin version
package/llms.txt CHANGED
@@ -3,7 +3,7 @@
3
3
  ## Overview
4
4
  spec-superflow is a self-contained workflow integration plugin for Claude Code, Cursor, OpenAI Codex CLI/App, GitHub Copilot CLI, Gemini CLI, OpenCode, WorkBuddy, and Trae. It merges spec-driven planning artifacts (proposal, specs, design, tasks) with disciplined execution guardrails (TDD, review gates, controlled handoff) into one unified workflow.
5
5
 
6
- Current version: v0.29.2.
6
+ Current version: v0.31.0.
7
7
 
8
8
  ## Key Documents
9
9
  - README.md: Chinese homepage with full usage guide and FAQ
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xulthekl/team-flow",
3
- "version": "0.29.2",
4
- "description": "Unified plugin (23 skills + 10 agents) integrating team-flow, compound-engineering, architecture-design, prototype, design-system, workflow-orchestrator, workflow-bootstrap, e2e, session-handoff, workflow-feedback for multi-agent coding tools.",
3
+ "version": "0.31.0",
4
+ "description": "Unified plugin (23 skills + 15 agents) integrating team-flow, compound-engineering, architecture-design, prototype, design-system, workflow-orchestrator, workflow-bootstrap, e2e, session-handoff, workflow-feedback for multi-agent coding tools.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "team-flow",
3
- "version": "0.29.2",
4
- "description": "Unified workflow plugin: team-flow (spec-driven dev: TDD/SDD/code-review/debugging) + compound-engineering core subset (brainstorm/plan/compound/strategy/ideate/proof, global compounding) + architecture-design (4A+DDD incremental design & global compounding) + prototype (local HTML prototype, zero external deps) + e2e (Playwright E2E, AC-driven) + workflow-orchestrator (product-level workflow orchestration) + workflow-bootstrap (existing project onboarding) + session-handoff (context transfer) + workflow-feedback (issue tracking). 23 skills + 10 agents, one install.",
3
+ "version": "0.31.0",
4
+ "description": "Unified workflow plugin: team-flow (spec-driven dev: TDD/SDD/code-review/debugging) + compound-engineering core subset (brainstorm/plan/compound/strategy/ideate/proof, global compounding) + architecture-design (4A+DDD incremental design & global compounding) + prototype (local HTML prototype, zero external deps) + e2e (Playwright E2E, AC-driven) + workflow-orchestrator (product-level workflow orchestration) + workflow-bootstrap (existing project onboarding) + session-handoff (context transfer) + workflow-feedback (issue tracking). 24 skills + 15 agents, one install.",
5
5
  "author": {
6
6
  "name": "LT"
7
7
  },
@@ -6,66 +6,224 @@
6
6
  //
7
7
  // Usage: node ensure-branch.mjs <change-dir> [change-name] [--force]
8
8
  //
9
+ // 布局识别(来源:workflow-feedback 2026-08-01 #100009):
10
+ // Case A 多仓库工作区 —— change-dir 形如 <root>/changes/<name>,且 <root> 下
11
+ // 存在含 .git 的直接子目录(真正的业务代码仓库,如 bff-*/ui-*/prototype/)。
12
+ // 工作区根本身往往只是近空壳 git 仓库(仅跟踪 .gitignore,无 remote)。旧实现
13
+ // 把 change-dir 当代码仓库、worktree 建在 ../,结果是空工作区副本,实现子代理
14
+ // 看不到任何参考代码。新实现为每个代码仓库在 <root>/.worktrees/<change>/<repo>
15
+ // 建立 worktree(基于各仓库当前分支),实现子代理进入 <root>/.worktrees/<change>/
16
+ // 即可看到与主工作区一致的目录结构和全部参考代码。
17
+ // Case B 单代码仓库(遗留兼容)—— 不满足 Case A 时回退到旧行为:把 change-dir
18
+ // 当代码仓库,worktree 建在 ../<repo>-<name>。保证单仓库项目不被破坏。
19
+ //
9
20
  // Security: every git invocation uses execFileSync with a LITERAL command
10
- // ('git') and a LITERAL argument array (no shell, no variable args array) —
11
- // the same form proven safe by install-cursor.mjs / install.mjs. There is no
12
- // string-form shell command, no variable command, and no dynamic args array.
21
+ // ('git') and a LITERAL argument array (no shell, no string-form command) — the
22
+ // same form proven safe by install-cursor.mjs / install.mjs. Worktree paths and
23
+ // branch names derive from controlled sources (change-name / repository directory
24
+ // names) and are passed as array elements, never interpolated into a shell string;
25
+ // branch names are additionally sanitized to git-legal characters before use.
13
26
  import { execFileSync } from 'node:child_process';
27
+ import { resolve, basename, dirname, join } from 'node:path';
28
+ import { readdirSync, existsSync, appendFileSync, readFileSync } from 'node:fs';
14
29
 
15
- const changeDir = process.argv[2];
16
- const changeName = process.argv[3];
30
+ const rawChangeDir = process.argv[2];
31
+ // 第三个位置参数是可选 change-name;若它其实是 '--force' 之类的开关则忽略,避免把
32
+ // 开关误当分支名(经 cmd-isolate 调用时 parseArgs 已正确分离,此为直接调用的兜底)。
33
+ let changeName = process.argv[3];
34
+ if (changeName && changeName.startsWith('--')) changeName = undefined;
17
35
  const force = process.argv.includes('--force');
18
36
 
19
- if (!changeDir) {
37
+ if (!rawChangeDir) {
20
38
  console.error('Usage: node ensure-branch.mjs <change-dir> [change-name] [--force]');
21
39
  process.exit(2);
22
40
  }
23
41
 
42
+ // 解析为绝对路径,后续所有路径计算基于此(来源:workflow-feedback 2026-08-01 #100009)。
43
+ const changeDir = resolve(rawChangeDir);
44
+
24
45
  const PROTECTED = ['main', 'master'];
25
- const GIT_OPTS = { encoding: 'utf-8', cwd: changeDir, stdio: ['ignore', 'pipe', 'pipe'] };
26
-
27
- // Determine current branch (literal arg array).
28
- let branch = '';
29
- try {
30
- branch = (execFileSync('git', ['branch', '--show-current'], GIT_OPTS) || '').trim();
31
- } catch {
32
- console.error('ensure-branch: could not determine current git branch. Is <change-dir> inside a git repository?');
33
- process.exit(1);
46
+
47
+ // 将分支名清洗为 git 合法字符(来源:workflow-feedback 2026-08-01 #100009)。
48
+ // git-check-ref-format 禁止空格 ~ ^ : ? * [ \ .. @{ 等,且不能以 . 开头/结尾、
49
+ // 不能 .lock 结尾。这里把非法字符替换为 '-',去掉首尾的 . / -,避免分支名以 '-'
50
+ // 开头被 git 误解析为命令行选项。输入来自受控来源,清洗是额外的纵深防护层。
51
+ function sanitizeBranchName(raw) {
52
+ const cleaned = String(raw)
53
+ .replace(/[^A-Za-z0-9._/-]/g, '-') // 非法字符 '-'
54
+ .replace(/\.{2,}/g, '.') // '..' → '.'(git 禁止 '..')
55
+ .replace(/\.lock$/i, '') // 禁止 '.lock' 结尾
56
+ .replace(/\/{2,}/g, '/') // 折叠多重 '/'
57
+ .replace(/^[./-]+/, '') // 去掉开头 . / -(git 禁止 . 开头;避免 '-' 开头当选项)
58
+ .replace(/[/.]+$/, ''); // 去掉结尾 / .
59
+ return cleaned || 'change';
60
+ }
61
+
62
+ // 若 change-dir 形如 <root>/changes/<name>,返回 <root>;否则返回 null。
63
+ function detectWorkspaceRoot(absChangeDir) {
64
+ const parent = dirname(absChangeDir); // <root>/changes
65
+ if (basename(parent) !== 'changes') return null;
66
+ return dirname(parent); // <root>
34
67
  }
35
68
 
36
- if (!PROTECTED.includes(branch)) {
37
- console.log(`ensure-branch: already isolated on branch '${branch}'. Proceed with implementation edits.`);
38
- process.exit(0);
69
+ // 枚举 root 下含 .git 的直接子目录(= 代码仓库),按目录名排序保证确定性。
70
+ // .git 可能是目录(普通仓库)或文件(worktree/submodule 链接),existsSync 均覆盖。
71
+ function listCodeRepos(root) {
72
+ let entries;
73
+ try {
74
+ entries = readdirSync(root, { withFileTypes: true });
75
+ } catch {
76
+ return [];
77
+ }
78
+ return entries
79
+ .filter((e) => e.isDirectory() && existsSync(join(root, e.name, '.git')))
80
+ .map((e) => e.name)
81
+ .sort();
39
82
  }
40
83
 
41
- console.error(`ensure-branch: on protected branch '${branch}'. Creating an isolated implementation context...`);
84
+ // 确保 <root>/.gitignore 含忽略 .worktrees/ 的行,已有则不重复追加。
85
+ function ensureWorktreeGitignore(root) {
86
+ const gitignorePath = join(root, '.gitignore');
87
+ let existing = '';
88
+ try {
89
+ existing = readFileSync(gitignorePath, 'utf-8');
90
+ } catch {
91
+ existing = '';
92
+ }
93
+ const alreadyIgnored = existing
94
+ .split(/\r?\n/)
95
+ .map((l) => l.trim())
96
+ .some((l) => l === '.worktrees/' || l === '.worktrees' || l === '/.worktrees/' || l === '/.worktrees');
97
+ if (alreadyIgnored) return;
98
+ const needsNewline = existing.length > 0 && !existing.endsWith('\n');
99
+ appendFileSync(gitignorePath, `${needsNewline ? '\n' : ''}.worktrees/\n`);
100
+ console.log(`ensure-branch: added '.worktrees/' to ${gitignorePath}.`);
101
+ }
102
+
103
+ // Case A:多仓库工作区隔离(来源:workflow-feedback 2026-08-01 #100009)。
104
+ // 为每个代码仓库在 <root>/.worktrees/<change>/<repo> 建 worktree(基于各仓库当前分支)。
105
+ function runMultiRepo(root, repos) {
106
+ const changeSlug = basename(changeDir);
107
+ const branchBase = sanitizeBranchName(changeName || changeSlug);
108
+ const changeWorktreeDir = join(root, '.worktrees', changeSlug);
109
+
110
+ console.error(`ensure-branch: multi-repo workspace detected at ${root}. Isolating ${repos.length} code repo(s): ${repos.join(', ')}...`);
111
+
112
+ // 先确保 .gitignore 忽略 .worktrees/,避免 worktree 产物污染工作区根仓库。
113
+ ensureWorktreeGitignore(root);
114
+
115
+ const failed = [];
116
+ for (const repo of repos) {
117
+ const repoDir = join(root, repo);
118
+ const repoOpts = { encoding: 'utf-8', cwd: repoDir, stdio: ['ignore', 'pipe', 'pipe'] };
119
+ const wtPath = join(changeWorktreeDir, repo);
120
+
121
+ // 幂等:worktree 已存在则复用,避免实现子代理重试时报 "already exists"。
122
+ if (existsSync(join(wtPath, '.git'))) {
123
+ console.log(`ensure-branch: [${repo}] worktree already exists at ${wtPath}; reusing.`);
124
+ continue;
125
+ }
42
126
 
43
- const repoName = changeDir.split('/').filter(Boolean).pop() || 'repo';
44
- const name = changeName || repoName;
45
- const worktreePath = `../${repoName}-${name}`;
127
+ // 逐仓库判断当前分支(字面参数数组)。
128
+ let branch = '';
129
+ try {
130
+ branch = (execFileSync('git', ['branch', '--show-current'], repoOpts) || '').trim();
131
+ } catch {
132
+ console.error(`ensure-branch: [${repo}] could not determine current git branch; cannot isolate.`);
133
+ failed.push(repo);
134
+ continue;
135
+ }
46
136
 
47
- // Preferred: git worktree (literal arg array).
48
- try {
49
- execFileSync('git', ['worktree', 'add', worktreePath, '-b', name], { ...GIT_OPTS, stdio: 'inherit' });
50
- console.log(`ensure-branch: created git worktree at ${worktreePath} on branch '${name}'. Make all implementation edits there.`);
51
- process.exit(0);
52
- } catch (e) {
53
- console.error(`ensure-branch: worktree creation failed: ${(e.stderr || e.stdout || e.message || 'unknown').toString().trim()}`);
137
+ // 该仓库已不在受保护分支 已隔离,无需建 worktree(沿用单仓库 already-isolated 语义)。
138
+ if (!PROTECTED.includes(branch)) {
139
+ console.log(`ensure-branch: [${repo}] already isolated on branch '${branch}'.`);
140
+ continue;
141
+ }
142
+
143
+ // 在受保护分支 worktree,基于该仓库当前分支(字面参数数组)。
144
+ // 不同代码仓库相互独立,使用同一分支名 branchBase 不会冲突。
145
+ try {
146
+ execFileSync('git', ['worktree', 'add', wtPath, '-b', branchBase], { ...repoOpts, stdio: 'inherit' });
147
+ console.log(`ensure-branch: [${repo}] created worktree at ${wtPath} on branch '${branchBase}'.`);
148
+ } catch (e) {
149
+ console.error(`ensure-branch: [${repo}] worktree creation failed: ${(e.stderr || e.stdout || e.message || 'unknown').toString().trim()}`);
150
+ failed.push(repo);
151
+ }
152
+ }
153
+
154
+ // 退出码语义与单仓库一致:全部隔离成功 → 0;有失败 + --force → 0(警告);有失败 + 无 force → 1(STOP)。
155
+ if (failed.length === 0) {
156
+ console.log(`ensure-branch: implementation subagents should enter ${changeWorktreeDir} to work; directory structure mirrors the main workspace, with reference code from every repo available.`);
157
+ process.exit(0);
158
+ }
159
+ if (force) {
160
+ console.error(`ensure-branch: WARNING — could not isolate ${failed.length} repo(s): ${failed.join(', ')}. Proceeding with --force; these remain on their protected branches.`);
161
+ process.exit(0);
162
+ }
163
+ console.error(`ensure-branch: could not isolate ${failed.length} repo(s): ${failed.join(', ')}. No --force given. STOP and ask the user for explicit approval before editing main/master.`);
164
+ process.exit(1);
54
165
  }
55
166
 
56
- // Fallback: local branch (literal arg array).
57
- try {
58
- execFileSync('git', ['switch', '-c', name], { ...GIT_OPTS, stdio: 'inherit' });
59
- console.log(`ensure-branch: created branch '${name}' via git switch -c. Make implementation edits there.`);
60
- process.exit(0);
61
- } catch (e) {
62
- console.error(`ensure-branch: branch creation failed: ${(e.stderr || e.stdout || e.message || 'unknown').toString().trim()}`);
167
+ // Case B:单代码仓库隔离(遗留兼容,保持重构前行为不变)。
168
+ // 把 change-dir 当代码仓库,worktree 建在 ../<repo>-<name>。
169
+ function runSingleRepo() {
170
+ const GIT_OPTS = { encoding: 'utf-8', cwd: changeDir, stdio: ['ignore', 'pipe', 'pipe'] };
171
+
172
+ // Determine current branch (literal arg array).
173
+ let branch = '';
174
+ try {
175
+ branch = (execFileSync('git', ['branch', '--show-current'], GIT_OPTS) || '').trim();
176
+ } catch {
177
+ console.error('ensure-branch: could not determine current git branch. Is <change-dir> inside a git repository?');
178
+ process.exit(1);
179
+ }
180
+
181
+ if (!PROTECTED.includes(branch)) {
182
+ console.log(`ensure-branch: already isolated on branch '${branch}'. Proceed with implementation edits.`);
183
+ process.exit(0);
184
+ }
185
+
186
+ console.error(`ensure-branch: on protected branch '${branch}'. Creating an isolated implementation context...`);
187
+
188
+ const repoName = basename(changeDir) || 'repo';
189
+ const name = sanitizeBranchName(changeName || repoName);
190
+ const worktreePath = `../${repoName}-${name}`;
191
+
192
+ // Preferred: git worktree (literal arg array).
193
+ try {
194
+ execFileSync('git', ['worktree', 'add', worktreePath, '-b', name], { ...GIT_OPTS, stdio: 'inherit' });
195
+ console.log(`ensure-branch: created git worktree at ${worktreePath} on branch '${name}'. Make all implementation edits there.`);
196
+ process.exit(0);
197
+ } catch (e) {
198
+ console.error(`ensure-branch: worktree creation failed: ${(e.stderr || e.stdout || e.message || 'unknown').toString().trim()}`);
199
+ }
200
+
201
+ // Fallback: local branch (literal arg array).
202
+ try {
203
+ execFileSync('git', ['switch', '-c', name], { ...GIT_OPTS, stdio: 'inherit' });
204
+ console.log(`ensure-branch: created branch '${name}' via git switch -c. Make implementation edits there.`);
205
+ process.exit(0);
206
+ } catch (e) {
207
+ console.error(`ensure-branch: branch creation failed: ${(e.stderr || e.stdout || e.message || 'unknown').toString().trim()}`);
208
+ }
209
+
210
+ // Both failed → require explicit approval to edit in place.
211
+ if (force) {
212
+ console.error('ensure-branch: WARNING — editing protected branch in place with --force. This modifies main/master directly.');
213
+ process.exit(0);
214
+ }
215
+ console.error('ensure-branch: could not create an isolated context and no --force given. STOP and ask the user for explicit approval before editing main/master.');
216
+ process.exit(1);
63
217
  }
64
218
 
65
- // Both failed → require explicit approval to edit in place.
66
- if (force) {
67
- console.error('ensure-branch: WARNING editing protected branch in place with --force. This modifies main/master directly.');
68
- process.exit(0);
219
+ // 主流程:布局识别后分发(来源:workflow-feedback 2026-08-01 #100009)。
220
+ const workspaceRoot = detectWorkspaceRoot(changeDir);
221
+ const codeRepos = workspaceRoot ? listCodeRepos(workspaceRoot) : [];
222
+
223
+ if (workspaceRoot && codeRepos.length > 0) {
224
+ // Case A:多仓库工作区。
225
+ runMultiRepo(workspaceRoot, codeRepos);
226
+ } else {
227
+ // Case B:不满足 Case A → 回退单代码仓库行为,保证单仓库项目不被破坏。
228
+ runSingleRepo();
69
229
  }
70
- console.error('ensure-branch: could not create an isolated context and no --force given. STOP and ask the user for explicit approval before editing main/master.');
71
- process.exit(1);
@@ -0,0 +1,67 @@
1
+ // scripts/guard/checks/test-matrix-complete.mjs — verify test matrix exists and is consistent
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { readState } from '../../lib/state-loader.mjs';
5
+ import { computeTestMatrixHash } from '../../lib/hash.mjs';
6
+
7
+ /**
8
+ * Check that test-matrix.md exists and its hash is consistent with state.
9
+ *
10
+ * Exemption paths (v0.12 §45.3):
11
+ * 1. Legacy: execution-contract.md has no "## Test Matrix" section → PASS
12
+ * 2. Explicit skip: test_matrix_skipped === 'true' → PASS
13
+ * 3. test-matrix.md exists and hash matches → PASS
14
+ * 4. test-matrix.md missing or hash mismatch → FAIL
15
+ *
16
+ * Returns { pass, failures[], reason? }.
17
+ */
18
+ export function checkTestMatrixComplete(changeDir) {
19
+ const state = readState(changeDir);
20
+
21
+ // Exemption 1: explicit skip
22
+ if (state.test_matrix_skipped === 'true') {
23
+ return { pass: true, failures: [], reason: 'explicitly skipped via test_matrix_skipped' };
24
+ }
25
+
26
+ // Exemption 2: legacy contract without Test Matrix section
27
+ const contractPath = path.join(changeDir, 'execution-contract.md');
28
+ if (fs.existsSync(contractPath)) {
29
+ const contract = fs.readFileSync(contractPath, 'utf-8');
30
+ if (!contract.includes('## Test Matrix')) {
31
+ return { pass: true, failures: [], reason: 'legacy contract — no test matrix section' };
32
+ }
33
+ } else {
34
+ // No contract at all — nothing to check
35
+ return { pass: true, failures: [], reason: 'no execution contract — skipping test matrix check' };
36
+ }
37
+
38
+ // Check test-matrix.md exists and is non-empty
39
+ const matrixPath = path.join(changeDir, 'test-matrix.md');
40
+ if (!fs.existsSync(matrixPath)) {
41
+ return {
42
+ pass: false,
43
+ failures: ['test-matrix.md is missing — contract declares ## Test Matrix but no matrix file found'],
44
+ };
45
+ }
46
+
47
+ const content = fs.readFileSync(matrixPath, 'utf-8');
48
+ if (content.trim().length === 0) {
49
+ return {
50
+ pass: false,
51
+ failures: ['test-matrix.md is empty — must contain at least a Summary section and Cases table'],
52
+ };
53
+ }
54
+
55
+ // Hash consistency check
56
+ const computed = computeTestMatrixHash(changeDir);
57
+ if (state.test_matrix_hash && state.test_matrix_hash !== computed) {
58
+ return {
59
+ pass: false,
60
+ failures: [
61
+ `test-matrix.md has been modified since last hash — stored: ${state.test_matrix_hash}, current: ${computed}. Run 'tf state rebuild' to update.`,
62
+ ],
63
+ };
64
+ }
65
+
66
+ return { pass: true, failures: [] };
67
+ }
@@ -14,6 +14,7 @@ import { checkDp3Approved } from './checks/dp3-approved.mjs';
14
14
  import { checkExecutionPlanReady } from './checks/execution-plan-ready.mjs';
15
15
  import { checkExecutionReviewsPassed } from './checks/execution-reviews-passed.mjs';
16
16
  import { checkCompoundCaptured } from './checks/compound-captured.mjs';
17
+ import { checkTestMatrixComplete } from './checks/test-matrix-complete.mjs';
17
18
 
18
19
  // Transition matrix: <from>:<to> → required check dimensions
19
20
  const TRANSITION_CHECKS = {
@@ -22,7 +23,7 @@ const TRANSITION_CHECKS = {
22
23
  'specifying:bridging': ['artifacts-exist', 'schema-valid'],
23
24
  'bridging:approved-for-build': ['artifacts-exist', 'schema-valid', 'contract-fresh', 'dp-gate-passed'],
24
25
  'approved-for-build:executing': ['artifacts-exist', 'contract-fresh', 'dp-gate-passed', 'execution-plan-ready'],
25
- 'executing:closing': ['tasks-complete', 'tests-passing', 'specs-merged', 'execution-plan-ready', 'execution-reviews-passed', 'compound-captured'],
26
+ 'executing:closing': ['tasks-complete', 'tests-passing', 'specs-merged', 'execution-plan-ready', 'execution-reviews-passed', 'compound-captured', 'test-matrix-complete'],
26
27
 
27
28
  // Debugging side-path
28
29
  'executing:debugging': [],
@@ -55,6 +56,8 @@ const WORKFLOW_TRANSITION_CHECKS = {
55
56
  'exploring:bridging': [],
56
57
  'bridging:approved-for-build': ['contract-current', 'dp3-approved'],
57
58
  'approved-for-build:executing': ['contract-current', 'dp3-approved', 'execution-plan-ready'],
59
+ // hotfix exemption: no test-matrix-complete (v0.12 §45.3)
60
+ 'executing:closing': ['tasks-complete', 'tests-passing', 'specs-merged', 'execution-plan-ready', 'execution-reviews-passed', 'compound-captured'],
58
61
  },
59
62
  tweak: {
60
63
  'exploring:approved-for-build': [],
@@ -167,6 +170,7 @@ async function main() {
167
170
  'execution-reviews-passed': (dir) => checkExecutionReviewsPassed(dir),
168
171
  'arch-design': (dir) => checkArchDesign(dir),
169
172
  'compound-captured': (dir) => checkCompoundCaptured(dir),
173
+ 'test-matrix-complete': (dir) => checkTestMatrixComplete(dir),
170
174
  };
171
175
 
172
176
  const checks = [];