@xulthekl/team-flow 0.29.1 → 0.30.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 (39) 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 +8 -3
  9. package/CHANGELOG.md +50 -0
  10. package/GEMINI.md +1 -1
  11. package/HANDOFF.md +4 -4
  12. package/INSTALL.md +1 -1
  13. package/README.md +4 -4
  14. package/agents/architecture-design.md +1 -0
  15. package/agents/build-executor.md +80 -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/lib/cmd-doctor.mjs +40 -1
  29. package/scripts/lib/cmd-state.mjs +24 -9
  30. package/scripts/lib/state-loader.mjs +22 -0
  31. package/skills/build-executor/SKILL.md +14 -4
  32. package/skills/contract-builder/SKILL.md +2 -0
  33. package/skills/need-explorer/SKILL.md +2 -0
  34. package/skills/prototype/references/orchestration-flow.md +8 -2
  35. package/skills/release-archivist/SKILL.md +2 -0
  36. package/skills/spec-writer/SKILL.md +2 -0
  37. package/skills/workflow-start/SKILL.md +29 -15
  38. package/skills/workflow-start/references/routing-rules.md +12 -6
  39. package/tests/lib/ensure-branch.test.mjs +52 -1
@@ -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.1`
129
+ - Current: `v0.30.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.1",
4
+ "version": "0.30.0",
5
5
  "contextFileName": "GEMINI.md"
6
6
  }
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env bash
2
- # v0.29.1: auto-sync CLI version with plugin version
2
+ # v0.30.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.1"
8
+ PLUGIN_VERSION="0.29.2"
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.1.
6
+ Current version: v0.30.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.1",
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.30.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.1",
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.30.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). 23 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);
@@ -3,6 +3,8 @@ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { loadConfig } from './config-loader.mjs';
5
5
  import { PLATFORM_RUNTIME_INVENTORY } from './platform-runtime-inventory.mjs';
6
+ // 非法 state 巡检所需的共享常量与读取器(来源:workflow-feedback 2026-08-01,#100004)。
7
+ import { readState, VALID_STATES } from './state-loader.mjs';
6
8
 
7
9
  const RUNTIME_SKILLS = new Set([
8
10
  'workflow-start', 'need-explorer', 'spec-writer', 'contract-builder',
@@ -220,6 +222,42 @@ function checkDocs(root) {
220
222
  return { pass: false, message: warnings.join('; ') };
221
223
  }
222
224
 
225
+ // 非法 state 巡检(来源:workflow-feedback 2026-08-01,#100004):
226
+ // 扫描 changes/<name>/.team-flow.yaml,若 state 字段不在 VALID_STATES 则报 FAIL。
227
+ // 非法值通常由子代理绕过 CLI 直接 Edit .team-flow.yaml 导致,提示修复为合法值。
228
+ // 沿用 checkSkills 的目录扫描 + 计数报告风格。
229
+ function checkChangeStates(root) {
230
+ const changesDir = join(root, 'changes');
231
+ if (!existsSync(changesDir)) {
232
+ return { pass: true, message: 'no changes/ directory (skipped)' };
233
+ }
234
+ const dirs = readdirSync(changesDir).filter(f => {
235
+ try { return statSync(join(changesDir, f)).isDirectory(); } catch { return false; }
236
+ });
237
+ const invalid = [];
238
+ let checked = 0;
239
+ for (const d of dirs) {
240
+ if (!existsSync(join(changesDir, d, '.team-flow.yaml'))) continue;
241
+ checked += 1;
242
+ const current = readState(join(changesDir, d)).state;
243
+ if (!VALID_STATES.includes(current)) {
244
+ invalid.push(`${d}='${current}'`);
245
+ }
246
+ }
247
+ if (checked === 0) {
248
+ return { pass: true, message: 'no state files under changes/ (skipped)' };
249
+ }
250
+ if (invalid.length > 0) {
251
+ return {
252
+ pass: false,
253
+ message:
254
+ `state 值非法(可能是子代理绕过 CLI 直接 Edit .team-flow.yaml 所致):${invalid.join('; ')}。` +
255
+ `请修复为合法值:${VALID_STATES.join('/')}`,
256
+ };
257
+ }
258
+ return { pass: true, message: `${checked} change(s) have legal state values` };
259
+ }
260
+
223
261
  export async function run(args) {
224
262
  const root = process.cwd();
225
263
  const config = loadConfig(root);
@@ -236,6 +274,7 @@ export async function run(args) {
236
274
  ['dist/', checkDist(root)],
237
275
  ['Node.js', checkNodeVersion()],
238
276
  ['Docs', checkDocs(root)],
277
+ ['Change states', checkChangeStates(root)],
239
278
  ];
240
279
 
241
280
  // Config check
@@ -264,4 +303,4 @@ export async function run(args) {
264
303
  }
265
304
  }
266
305
 
267
- export { checkVersionConsistency, checkHooks, checkCodexManifest, checkSkills, checkRuntimeDistribution, checkDist, checkRootPluginAuthor, checkNodeVersion, checkDocs };
306
+ export { checkVersionConsistency, checkHooks, checkCodexManifest, checkSkills, checkRuntimeDistribution, checkDist, checkRootPluginAuthor, checkNodeVersion, checkDocs, checkChangeStates };
@@ -2,18 +2,15 @@
2
2
  import { parseArgs } from 'node:util';
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import { existsSync, mkdirSync } from 'node:fs';
5
- import { dirname, join } from 'node:path';
5
+ import path, { dirname, join } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
- import { readState, writeState, updateField, rebuildState } from './state-loader.mjs';
7
+ // VALID_STATES 统一从 state-loader.mjs 引入(状态机合法值唯一真相源),不再本地硬编码。
8
+ // 来源:workflow-feedback 2026-08-01,#100005。
9
+ import { readState, writeState, updateField, rebuildState, VALID_STATES } from './state-loader.mjs';
8
10
  import { computeArtifactsHash, computeContractHash } from './hash.mjs';
9
11
 
10
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
13
 
12
- const VALID_STATES = [
13
- 'exploring', 'specifying', 'bridging', 'approved-for-build',
14
- 'executing', 'debugging', 'closing', 'abandoned',
15
- ];
16
-
17
14
  const SETTABLE_FIELDS = [
18
15
  'workflow', 'test_result', 'batches_completed', 'spec_merged',
19
16
  'dp_0_decisions', 'dp_0_confirmed', 'dp_0_timestamp', 'dp_0_result',
@@ -44,7 +41,7 @@ export async function run(args) {
44
41
  });
45
42
 
46
43
  const sub = positionals[0]; // init | check | transition | get | rebuild | set
47
- const changeDir = positionals[1];
44
+ let changeDir = positionals[1];
48
45
  const arg = positionals[2]; // <to-state> for transition, <field> for get
49
46
 
50
47
  if (!changeDir) {
@@ -53,6 +50,13 @@ export async function run(args) {
53
50
  process.exit(2);
54
51
  }
55
52
 
53
+ // 修复相对路径 bug(来源:workflow-feedback 2026-08-01,#100005)。
54
+ // 根因:readState 用 path.join(changeDir,...) 按用户 cwd 解析,而 transition 分支 spawn guard
55
+ // 子进程时 cwd 设为包根(join(__dirname,'..','..'))+ 原样 changeDir,两处基准不一致——用户传
56
+ // 相对路径时 guard 在包根下找不到产物。统一在入口 resolve 为绝对路径,使后续 readState /
57
+ // guard spawn / existsSync 全部基于同一绝对基准,消除 cwd 歧义。
58
+ changeDir = path.resolve(changeDir);
59
+
56
60
  // Unknown subcommand: report a usage error (exit 2) BEFORE the BUG-B
57
61
  // state-file existence check, so a bad subcommand is not masked by the
58
62
  // "No state file" error (which would return exit 1 instead of exit 2).
@@ -222,7 +226,18 @@ export async function run(args) {
222
226
  process.exit(2);
223
227
  }
224
228
  if (!SETTABLE_FIELDS.includes(field)) {
225
- console.error(`⛔ Field '${field}' is not settable (use 'transition' for state, or check SETTABLE_FIELDS)`);
229
+ // 错误提示升级(来源:workflow-feedback 2026-08-01,#100005):
230
+ // 'state' 是最常被误用 set 的字段,专门给出完整 transition 语法示例;合法状态清单
231
+ // 引用 VALID_STATES(唯一真相源),不硬编码。保留 'not settable' 措辞以兼容既有回归测试。
232
+ if (field === 'state') {
233
+ console.error(
234
+ `⛔ 'state' 不是可 set 的字段(not settable)。状态转换请用完整语法:\n` +
235
+ ` tf state transition <change-dir 绝对路径> <目标状态>\n` +
236
+ ` 合法状态:${VALID_STATES.join('/')}`
237
+ );
238
+ } else {
239
+ console.error(`⛔ Field '${field}' is not settable (use 'transition' for state, or check SETTABLE_FIELDS)`);
240
+ }
226
241
  process.exit(1);
227
242
  }
228
243
  updateField(changeDir, field, value);
@@ -4,6 +4,14 @@ import path from 'node:path';
4
4
 
5
5
  const STATE_FILE = '.team-flow.yaml';
6
6
 
7
+ // 状态机合法值唯一真相源 (来源:workflow-feedback 2026-08-01,#100005)。
8
+ // 所有状态枚举校验统一引用此常量,禁止在各处硬编码状态字符串数组,避免多处定义漂移。
9
+ // cmd-state.mjs(transition 校验 / set 报错)、cmd-doctor.mjs(非法 state 巡检)均从这里 import。
10
+ export const VALID_STATES = [
11
+ 'exploring', 'specifying', 'bridging', 'approved-for-build',
12
+ 'executing', 'debugging', 'closing', 'abandoned',
13
+ ];
14
+
7
15
  const BUILTIN_DEFAULTS = {
8
16
  state: 'exploring',
9
17
  workflow: 'auto',
@@ -74,6 +82,20 @@ export function readState(changeDir) {
74
82
  * Write state object to .team-flow.yaml.
75
83
  */
76
84
  export function writeState(changeDir, state) {
85
+ // Defense-in-depth 状态合法性校验 (来源:workflow-feedback 2026-08-01,#100004)。
86
+ // 主修复是子代理 prompt 禁令(禁止绕过 CLI 直接 Edit .team-flow.yaml);此处是最后一道防线,
87
+ // 拦截任何非法 state 落盘。init/transition/rebuild/updateField 等 CLI 路径产生的 state 均合法
88
+ // (缺失时回退 'exploring'),不会误伤;仅当出现非法值时抛错,提示用 tf doctor 排查。
89
+ const effectiveState = state.state || 'exploring';
90
+ if (!VALID_STATES.includes(effectiveState)) {
91
+ throw new Error(
92
+ `Refusing to write illegal state '${effectiveState}' to ${path.join(changeDir, STATE_FILE)}. ` +
93
+ `Legal states: ${VALID_STATES.join(', ')}. ` +
94
+ `This is usually caused by a subagent bypassing the CLI and editing .team-flow.yaml directly — ` +
95
+ `restore a legal value or run 'tf doctor' to diagnose.`
96
+ );
97
+ }
98
+
77
99
  const filePath = path.join(changeDir, STATE_FILE);
78
100
  const lines = [];
79
101
  lines.push('# .team-flow.yaml — lightweight state machine');
@@ -27,17 +27,27 @@ Branch/worktree preflight before ANY implementation edit (mandatory — do not s
27
27
  ```bash
28
28
  tf isolate <change-dir>
29
29
  ```
30
- This script enforces git isolation: if you are on `main`/`master` it creates a
31
- git worktree (preferred) or a new branch, and exits non-zero if it cannot and you
32
- have not approved `--force`.
30
+ This script enforces git isolation. In a **multi-repo workspace** (a workspace root
31
+ whose direct subdirectories are independent code repositories, e.g. `bff-*`/`ui-*`/
32
+ `prototype/`, while the root itself is often a near-empty git repo), it creates one git
33
+ worktree per code repo under `<workspace>/.worktrees/<change>/<repo>` — each based on
34
+ that repo's current branch — so the implementation subagent enters that aggregated
35
+ directory and sees reference code from every repo with the same layout as the main
36
+ workspace. In a **single code repository** it falls back to creating one git worktree
37
+ (preferred) or a new branch. It exits non-zero if it cannot isolate and you have not
38
+ approved `--force`.
33
39
  2. If `tf isolate` exits non-zero: STOP. Do not edit `main`/`master` in place.
34
40
  Ask the user for explicit approval (and re-run with `tf isolate <change-dir> --force`
35
41
  only after they approve).
36
- 3. If it succeeds, report the chosen branch/worktree and make all implementation
42
+ 3. If it succeeds, report the chosen branch/worktree in a multi-repo workspace, the
43
+ aggregated `<workspace>/.worktrees/<change>/` directory — and make all implementation
37
44
  edits there.
38
45
 
39
46
  ## Core Laws
40
47
 
48
+ ### Law 0: State Field Boundary (v0.30.0)
49
+ 本 skill 仅通过 `tf state set` 写自己的 `dp_5_*` 决策字段;**MUST NOT** 修改 `.team-flow.yaml` 的 `state`/`workflow` 核心字段——状态转换是主代理(workflow-start)经 `tf state transition` 的专有职责(来源:workflow-feedback 2026-08-01)。
50
+
41
51
  ### Law 1: Contract First
42
52
  The execution contract is the approved handoff artifact, not chat history.
43
53
 
@@ -53,6 +53,8 @@ Generate minimal contract: Intent Lock (one sentence), Task List (numbered), App
53
53
 
54
54
  ## Guardrails
55
55
 
56
+ - **状态字段禁写(v0.30.0)**:仅写本 skill 的 `dp_3_*` 决策字段;**MUST NOT** 修改 `state`/`workflow` 核心字段——状态转换由主代理经 `tf state transition` 执行。`tf state init` 只创建状态文件,不改 `state` 值(来源:workflow-feedback 2026-08-01)。
57
+
56
58
  - Do not continue to implementation if ambiguity remains
57
59
  - Do not approve the contract on the user's behalf
58
60
  - Do not skip the contract because planning docs look complete
@@ -52,6 +52,8 @@ Once DP-1 is recorded, hand off to `spec-writer`.
52
52
 
53
53
  ## Anti-Patterns
54
54
 
55
+ - **修改 `.team-flow.yaml` 的 `state`/`workflow` 核心字段**(v0.30.0 禁止):状态转换是主代理经 `tf state transition` 的专有职责;need-explorer 只写自己的 `dp_1_*` 决策字段(来源:workflow-feedback 2026-08-01)。
56
+
55
57
  - **Skipping exploration**: "Simple" changes have scope too. Five minutes of exploration prevents two hours of rework.
56
58
  - **Proposing solutions before clarifying**: If the user says "add caching," first ask what problem caching solves.
57
59
  - **Exploring indefinitely**: Stop when change name, problem statement, scope, non-goals, success criteria, and decomposition decision are all clear.