@xulthekl/team-flow 0.36.3 → 0.37.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.
- package/.claude/always/phase-guard.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/plugin/marketplace.json +2 -2
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +17 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +1 -1
- package/docs/README_en.md +1 -1
- package/gemini-extension.json +1 -1
- package/hooks/session-start +2 -2
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/scripts/lib/cmd-prototype.mjs +227 -0
- package/scripts/lib/cmd-publish.mjs +254 -0
- package/scripts/lib/prototype-sync.mjs +41 -2
- package/scripts/team-flow.mjs +8 -0
- package/skills/architecture-design/references/s3.5-product-architecture.md +9 -0
- package/skills/build-executor/SKILL.md +7 -0
- package/skills/contract-builder/SKILL.md +13 -0
- package/skills/e2e/SKILL.md +2 -0
- package/skills/prototype/SKILL.md +1 -0
- package/skills/release-archivist/SKILL.md +9 -0
- package/skills/spec-writer/SKILL.md +11 -7
- package/skills/workflow-orchestrator/references/s2-prd-prototype-loop.md +9 -0
- package/skills/workflow-orchestrator/references/s4-split-validate.md +9 -0
- package/skills/workflow-start/SKILL.md +12 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "team-flow",
|
|
11
11
|
"description": "8-state spec workflow + compound global compounding + architecture-design (4A/DDD) + local HTML prototype + product-level orchestration + bootstrap + e2e + session handoff + workflow feedback. 24 skills + 15 agents with embedded TDD, SDD, code review, debugging, delta spec sync, and design-system-driven prototyping.",
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.37.0",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"author": {
|
|
15
15
|
"name": "LT",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "team-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
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
|
"source": "./",
|
|
6
6
|
"author": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "team-flow",
|
|
3
3
|
"displayName": "team-flow",
|
|
4
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
|
-
"version": "0.
|
|
5
|
+
"version": "0.37.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "LT",
|
|
8
8
|
"url": "https://github.com/LT"
|
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Unified workflow plugins and skills for AI coding agents (team-flow: team-flow + compound + architecture-design + prototype).",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.37.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "team-flow",
|
|
14
14
|
"description": "Unified workflow with planning artifacts, execution contracts, TDD, review gates, systematic debugging, delta spec sync, architecture-design, and local HTML prototyping.",
|
|
15
|
-
"version": "0.
|
|
15
|
+
"version": "0.37.0",
|
|
16
16
|
"source": ".",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "LT",
|
package/AGENTS.md
CHANGED
|
@@ -49,6 +49,7 @@ spec 驱动开发过程。8 态变更机:`exploring → specifying → bridgin
|
|
|
49
49
|
|
|
50
50
|
### 4. prototype(1 skill,本地 HTML 原型,v0.15.0 内部编排重构)
|
|
51
51
|
- 全局一份 `prototype/`(与 PRD 同级,git 分支隔离),零外部依赖、可离线
|
|
52
|
+
- **v0.37.0 版本 worktree 隔离**:原型独立仓库(有远程),版本分支 `prd-vN` 的 worktree 由 `tf prototype branch <prd-vN>` 创建/复用(隔离 + 团队拉取),版本收尾 `tf prototype deisolate <prd-vN> --merge --clean` merge 回主干(§68.4);原型 worktree 布局 `<workspace>/.worktrees/<prd-vN>/prototype/`
|
|
52
53
|
- **内部编排器(v0.15.0)**:主代理只编排不实施——环境探查(`prototype-env-scout`)→方案/人工评审→绘制(`prototype-builder`)→评审(`prototype-reviewer`)→循环→人工路由
|
|
53
54
|
- **v0.20.0 产出质量闸门**:builder→reviewer 间主代理强制 `test -f && test -s` 校验入口产物落盘(不信 status=done 字面值,缺失则 resume,不空转评审);后台子代理依赖 `<task-notification>` 完成通知,禁反复 `TaskOutput(block=true)` 轮询(设计 §22)
|
|
54
55
|
- **v0.21.0 决策点中继(stop-and-resume)**:子代理(无 AskUserQuestion)决策点经 `SendMessage(to: main)` 发问即停,主代理收 task-notification 代问用户、回传自动 resume 子代理续跑;主代理须维护请求-应答匹配(设计 §22.1.1,修订 §18.1.2,LT 实证确认)
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ The format loosely follows Keep a Changelog.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.37.0] - 2026-08-05
|
|
10
|
+
|
|
11
|
+
### Added(阶段同步 + 原型版本隔离,设计增强方案 v0.15 §68)
|
|
12
|
+
|
|
13
|
+
- **CLI 新命令 `tf publish`**:阶段产物白名单提交 + 可选推送(`--prd / --arch / --changes <dir> / --all` + `--push` 确认制 + `--dry-run`);复用 arch-merge 白名单 commit 模式 + 外部脏文件检测 + 独立写锁(`.publish.lock`)
|
|
14
|
+
- **CLI 新命令 `tf prototype branch` / `tf prototype deisolate`**:原型仓库(独立 + 远程)PRD 版本 worktree 隔离——`branch <prd-vN>` 创建/复用版本 worktree(隔离 + 团队一键拉取);`deisolate <prd-vN> --merge --clean` 版本收尾 merge 回主干 + 清理(方案 A:merge 时机 = 版本收尾)
|
|
15
|
+
- **5 个同步门禁点(G1-G5,§68.2)**:S2 冻结 / ARCH 完成 / orchestrator 结束 / execution-contract 完成 / change closing——各点 AskUserQuestion 三选项(提交并推送 / 仅提交 / 暂不同步),团队协作阶段产物同步
|
|
16
|
+
- **制品链路径版本化修正(§68.5)**:原型引用 = 语义相对 + 版本绑定;spec-writer / build-executor / e2e / contract-builder 声明原型版本 worktree 路径;`prototype-sync --prototype-dir` 缺省按版本解析 worktree(`derivePrdVersion`/`resolvePrototypeDir`,change-brief `upstream_plan_ref` 推导,worktree 缺失回退主仓库并提示)
|
|
17
|
+
- **文档**:设计增强方案 v0.15(§68 阶段同步 + 原型版本隔离 + §69 决策落实状态)
|
|
18
|
+
|
|
19
|
+
## [0.36.4] - 2026-08-05
|
|
20
|
+
|
|
21
|
+
### Added(Wave 审查纪律 + 上下文轮转)
|
|
22
|
+
|
|
23
|
+
- **workflow-start SKILL.md**:Route to code-reviewer 增加逐 wave 审查纪律——每次 code-reviewer dispatch 只审查一个 wave,禁止合并多 wave 审查(vrm4teamflow C1 事件修复)
|
|
24
|
+
- **workflow-start SKILL.md**:Sub-agent Dispatch Protocol 增加 build-executor 交互轮次轮转——累计 ≥6 轮 SendMessage 交互时启动新 build-executor 子代理(交接文档必须含已完成 waves + 累积决策 + worktree SHA)
|
|
25
|
+
|
|
9
26
|
## [0.36.3] - 2026-08-05
|
|
10
27
|
|
|
11
28
|
### Fixed(v0.14 全链路缺口修复——27 项,复利链路 + agent 契约 + 门禁时序 + 交接/决策门 + guard hook + 快照生命周期 + 旧项目 + 文档同步 + 横展)
|
package/GEMINI.md
CHANGED
|
@@ -8,7 +8,7 @@ The workflow is self-contained and does not require OpenSpec or Superpowers at r
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
<!-- team-flow-phase-guard-start -->
|
|
11
|
-
# team-flow v0.
|
|
11
|
+
# team-flow v0.37.0 | 阶段: {{state}} | 工作流: {{workflow}}
|
|
12
12
|
当前阶段允许的操作由 workflow-start 路由规则定义。
|
|
13
13
|
禁止跨越 DP gate 进入下一阶段。变更范围以 execution-contract.md 的 Intent Lock 为准。
|
|
14
14
|
<!-- team-flow-phase-guard-end -->
|
package/INSTALL.md
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# team-flow
|
|
2
2
|
|
|
3
|
-
> 当前版本:`v0.
|
|
3
|
+
> 当前版本:`v0.37.0`
|
|
4
4
|
|
|
5
5
|
> 统一插件:**team-flow**(spec 驱动开发)+ **compound-engineering 核心子集**(全局复利)+ **architecture-design**(4A+DDD 增量设计)+ **prototype**(本地 HTML 原型)+ **e2e**(AC 驱动 E2E)+ **workflow-orchestrator**(产品级编排)+ **workflow-bootstrap**(既有项目接入)。一次安装,七套能力协同。
|
|
6
6
|
|
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.
|
|
129
|
+
- Current: `v0.37.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)
|
package/gemini-extension.json
CHANGED
|
@@ -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). 24 skills, one install.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.37.0",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
package/hooks/session-start
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# v0.
|
|
2
|
+
# v0.37.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.
|
|
8
|
+
PLUGIN_VERSION="0.37.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.
|
|
6
|
+
Current version: v0.37.0.
|
|
7
7
|
|
|
8
8
|
## Key Documents
|
|
9
9
|
- README.md: Chinese homepage with full usage guide and FAQ
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xulthekl/team-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Unified plugin (24 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",
|
package/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "team-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
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"
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* prototype — 原型仓库版本 worktree 隔离(v0.37.0 §68.4)
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* tf prototype branch <prd-vN> # 创建/复用原型版本 worktree(隔离 + 团队拉取)
|
|
7
|
+
* tf prototype deisolate <prd-vN> [--merge] [--clean]
|
|
8
|
+
* # 版本收尾:merge 回主干 + 清理 worktree
|
|
9
|
+
*
|
|
10
|
+
* 设计(v0.15 §68.4/§68.6):
|
|
11
|
+
* - 原型为独立 git 仓库(有远程),位置约定 = <workspace>/prototype/
|
|
12
|
+
* - 隔离单位 = PRD 版本(分支 `prd-vN`),worktree 布局 = <workspace>/.worktrees/prd-vN/prototype/
|
|
13
|
+
* (与代码实施 ensure-branch 的 .worktrees/ 统一隔离区同构,gitignored)
|
|
14
|
+
* - branch:幂等(已有 worktree 复用);从远程拉 prd-vN(远程存在)或从主干创建(不存在)
|
|
15
|
+
* —— worktree 命令同时是团队查看者的一键拉取入口(§68.6)
|
|
16
|
+
* - deisolate:版本收尾(change_dag 该版本全部 closing)时 merge `prd-vN` 回主干 + 清理
|
|
17
|
+
* (方案 A,LT 决策 2026-08-05:merge 时机 = 版本收尾,一次 merge 干净)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { execFileSync } from 'node:child_process';
|
|
21
|
+
import { existsSync, readdirSync, rmSync, appendFileSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { join, resolve, basename } from 'node:path';
|
|
23
|
+
|
|
24
|
+
const PROTECTED = ['main', 'master'];
|
|
25
|
+
|
|
26
|
+
/** git 执行封装(统一 encoding + 错误降级)。 */
|
|
27
|
+
function git(cwd, ...args) {
|
|
28
|
+
return execFileSync('git', args, { encoding: 'utf-8', cwd, stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 规范化 PRD 版本参数 → `prd-vN`。接受 "v2"/"2"/"prd-v2"。 */
|
|
32
|
+
function normalizeBranchName(raw) {
|
|
33
|
+
const cleaned = String(raw || '').trim();
|
|
34
|
+
if (!cleaned) throw new Error('缺少 PRD 版本参数(如 tf prototype branch v2)');
|
|
35
|
+
if (/^prd-v\d+/i.test(cleaned)) return cleaned.toLowerCase();
|
|
36
|
+
const v = cleaned.replace(/^v/i, '');
|
|
37
|
+
if (!/^\d+$/.test(v)) throw new Error(`非法 PRD 版本:${raw}(应为 vN,如 v2)`);
|
|
38
|
+
return `prd-v${v}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 定位 workspace root(cwd 在主仓库内)与原型仓库路径。 */
|
|
42
|
+
function resolveLayout() {
|
|
43
|
+
let workspaceRoot;
|
|
44
|
+
try {
|
|
45
|
+
workspaceRoot = git(process.cwd(), 'rev-parse', '--show-toplevel');
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error('无法定位 workspace root:cwd 不在 git 仓库内?');
|
|
48
|
+
}
|
|
49
|
+
const protoRepo = join(workspaceRoot, 'prototype');
|
|
50
|
+
if (!existsSync(join(protoRepo, '.git'))) {
|
|
51
|
+
throw new Error(`原型仓库不存在或非 git 仓库:${protoRepo}\n 约定:原型仓库 = <workspace>/prototype/(独立 git 仓库,有远程)`);
|
|
52
|
+
}
|
|
53
|
+
return { workspaceRoot, protoRepo };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ensureWorktreeGitignore(root) {
|
|
57
|
+
const gitignorePath = join(root, '.gitignore');
|
|
58
|
+
let existing = '';
|
|
59
|
+
try { existing = readFileSync(gitignorePath, 'utf-8'); } catch { existing = ''; }
|
|
60
|
+
const already = existing.split(/\r?\n/).map(l => l.trim()).some(l => l === '.worktrees/' || l === '.worktrees');
|
|
61
|
+
if (already) return;
|
|
62
|
+
appendFileSync(gitignorePath, `${existing.length > 0 && !existing.endsWith('\n') ? '\n' : ''}.worktrees/\n`);
|
|
63
|
+
console.log(`prototype: added '.worktrees/' to ${gitignorePath}.`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 检查远程 prd-vN 分支是否存在。 */
|
|
67
|
+
function remoteBranchExists(protoRepo, branch) {
|
|
68
|
+
try {
|
|
69
|
+
git(protoRepo, 'rev-parse', '--verify', `origin/${branch}`);
|
|
70
|
+
return true;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** 检查本地 prd-vN 分支是否存在。 */
|
|
77
|
+
function localBranchExists(protoRepo, branch) {
|
|
78
|
+
try {
|
|
79
|
+
git(protoRepo, 'rev-parse', '--verify', `refs/heads/${branch}`);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 子命令 branch:创建/复用原型版本 worktree。 */
|
|
87
|
+
function cmdBranch(branchName) {
|
|
88
|
+
const { workspaceRoot, protoRepo } = resolveLayout();
|
|
89
|
+
const wtPath = join(workspaceRoot, '.worktrees', branchName, basename(protoRepo));
|
|
90
|
+
|
|
91
|
+
// 幂等:worktree 已存在 → 复用
|
|
92
|
+
if (existsSync(join(wtPath, '.git'))) {
|
|
93
|
+
console.log(`prototype: worktree 已存在,复用 ${wtPath}`);
|
|
94
|
+
console.log(` 分支:${branchName}(原型版本 ${branchName} 的工作区)`);
|
|
95
|
+
return { created: false, reused: true, worktreePath: wtPath };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
ensureWorktreeGitignore(workspaceRoot);
|
|
99
|
+
|
|
100
|
+
// fetch 远程,获取团队已推送的 prd-vN
|
|
101
|
+
try { git(protoRepo, 'fetch', 'origin'); } catch { /* 远程不可达时降级为本地创建 */ }
|
|
102
|
+
|
|
103
|
+
const fromRemote = remoteBranchExists(protoRepo, branchName);
|
|
104
|
+
try {
|
|
105
|
+
if (fromRemote) {
|
|
106
|
+
git(protoRepo, 'worktree', 'add', wtPath, '-b', branchName, `origin/${branchName}`);
|
|
107
|
+
console.log(`prototype: 从远程 origin/${branchName} 创建 worktree ${wtPath}`);
|
|
108
|
+
} else {
|
|
109
|
+
git(protoRepo, 'worktree', 'add', wtPath, '-b', branchName);
|
|
110
|
+
console.log(`prototype: 从主干创建分支 ${branchName} 的 worktree ${wtPath}`);
|
|
111
|
+
console.log(` 提示:该版本原型首次创建,迭代完成后 push origin ${branchName} 供团队拉取`);
|
|
112
|
+
}
|
|
113
|
+
return { created: true, fromRemote, worktreePath: wtPath };
|
|
114
|
+
} catch (e) {
|
|
115
|
+
const msg = (e.stderr || e.stdout || e.message || '').toString().trim();
|
|
116
|
+
throw new Error(`worktree 创建失败:${msg}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** 子命令 deisolate:版本收尾 merge 回主干 + 清理 worktree。 */
|
|
121
|
+
function cmdDeisolate(branchName, opts) {
|
|
122
|
+
const { workspaceRoot, protoRepo } = resolveLayout();
|
|
123
|
+
const wtPath = join(workspaceRoot, '.worktrees', branchName, basename(protoRepo));
|
|
124
|
+
const result = { branch: branchName, worktreePath: wtPath };
|
|
125
|
+
|
|
126
|
+
if (!existsSync(join(wtPath, '.git'))) {
|
|
127
|
+
console.log(`prototype: ${branchName} 无 worktree(${wtPath} 不存在),无可清理。`);
|
|
128
|
+
return { found: false };
|
|
129
|
+
}
|
|
130
|
+
result.found = true;
|
|
131
|
+
|
|
132
|
+
const masterBranch = git(protoRepo, 'branch', '--show-current') || 'master';
|
|
133
|
+
result.master = masterBranch;
|
|
134
|
+
|
|
135
|
+
// ahead/behind 摘要
|
|
136
|
+
try {
|
|
137
|
+
const ahead = git(protoRepo, 'rev-list', '--count', `${masterBranch}..${branchName}`);
|
|
138
|
+
const behind = git(protoRepo, 'rev-list', '--count', `${branchName}..${masterBranch}`);
|
|
139
|
+
result.ahead = parseInt(ahead, 10);
|
|
140
|
+
result.behind = parseInt(behind, 10);
|
|
141
|
+
} catch { /* 分支可能未合并过,忽略 */ }
|
|
142
|
+
|
|
143
|
+
if (opts.merge) {
|
|
144
|
+
try {
|
|
145
|
+
git(protoRepo, 'merge', branchName, '--no-edit');
|
|
146
|
+
result.merged = true;
|
|
147
|
+
console.log(`✅ ${branchName} merged → ${masterBranch}(版本收尾,方案 A)`);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
result.merged = false;
|
|
150
|
+
result.mergeError = (e.stderr || e.stdout || e.message || '').toString().trim();
|
|
151
|
+
console.error(`❌ merge 失败:${result.mergeError}`);
|
|
152
|
+
console.error(' 存在冲突时请先人工解决再重试。');
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
console.log(` ahead=${result.ahead ?? '?'} behind=${result.behind ?? '?'}(相对 ${masterBranch})`);
|
|
156
|
+
console.log(' 未执行 merge(加 --merge 提交合并回主干)。');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (opts.clean) {
|
|
160
|
+
const errors = [];
|
|
161
|
+
try { git(protoRepo, 'worktree', 'remove', wtPath, '--force'); result.worktreeRemoved = true; }
|
|
162
|
+
catch {
|
|
163
|
+
try { rmSync(wtPath, { recursive: true, force: true }); result.worktreeRemoved = true; }
|
|
164
|
+
catch (e) { errors.push(`worktree remove: ${e.message}`); }
|
|
165
|
+
}
|
|
166
|
+
try { git(protoRepo, 'branch', '-d', branchName); result.branchDeleted = true; }
|
|
167
|
+
catch {
|
|
168
|
+
try { git(protoRepo, 'branch', '-D', branchName); result.branchDeleted = true; }
|
|
169
|
+
catch (e) { errors.push(`branch delete: ${e.message}`); }
|
|
170
|
+
}
|
|
171
|
+
if (errors.length > 0) console.error(` ⚠️ ${errors.join('; ')}`);
|
|
172
|
+
else console.log('✅ worktree + 分支已清理。');
|
|
173
|
+
} else {
|
|
174
|
+
console.log(' 未执行清理(加 --clean 删除 worktree + 分支)。');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function usage() {
|
|
181
|
+
console.log(`tf prototype — 原型仓库版本 worktree 隔离(v0.37.0 §68.4)
|
|
182
|
+
|
|
183
|
+
用法:
|
|
184
|
+
tf prototype branch <prd-vN> # 创建/复用原型版本 worktree(隔离 + 团队拉取)
|
|
185
|
+
tf prototype deisolate <prd-vN> [--merge] [--clean]
|
|
186
|
+
# 版本收尾:merge 回主干 + 清理 worktree
|
|
187
|
+
|
|
188
|
+
示例:
|
|
189
|
+
tf prototype branch v2 # 为 PRD v2 创建/复用 prd-v2 worktree
|
|
190
|
+
tf prototype branch prd-v2 # 同上(分支名已含前缀)
|
|
191
|
+
tf prototype deisolate v2 --merge --clean # v2 收尾:merge 回主干 + 清理
|
|
192
|
+
|
|
193
|
+
说明:
|
|
194
|
+
- 原型仓库约定:<workspace>/prototype/(独立 git 仓库,有远程)
|
|
195
|
+
- worktree 布局:<workspace>/.worktrees/<prd-vN>/prototype/(gitignored 隔离区)
|
|
196
|
+
- merge 时机 = 版本收尾(change_dag 该版本全部 closing),一次 merge 回主干`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** CLI 入口(export 供 team-flow.mjs 注册)。 */
|
|
200
|
+
export async function run(args = []) {
|
|
201
|
+
const [sub, rawVersion, ...rest] = args;
|
|
202
|
+
if (!sub || sub === '-h' || sub === '--help') { usage(); return; }
|
|
203
|
+
|
|
204
|
+
if (!rawVersion) {
|
|
205
|
+
console.error(`tf prototype ${sub} 缺少 PRD 版本参数(如 tf prototype ${sub} v2)`);
|
|
206
|
+
process.exit(2);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let branchName;
|
|
210
|
+
try { branchName = normalizeBranchName(rawVersion); }
|
|
211
|
+
catch (e) { console.error(e.message); process.exit(2); }
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
if (sub === 'branch') {
|
|
215
|
+
return cmdBranch(branchName);
|
|
216
|
+
}
|
|
217
|
+
if (sub === 'deisolate') {
|
|
218
|
+
const opts = { merge: rest.includes('--merge'), clean: rest.includes('--clean') };
|
|
219
|
+
return cmdDeisolate(branchName, opts);
|
|
220
|
+
}
|
|
221
|
+
console.error(`未知子命令:${sub}(可用:branch / deisolate)`);
|
|
222
|
+
process.exit(2);
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.error(`prototype: ${e.message}`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* publish — 阶段产物白名单提交 + 可选推送(v0.37.0 §68.3)
|
|
4
|
+
*
|
|
5
|
+
* 用法:tf publish <target> [--push] [--dry-run]
|
|
6
|
+
* target: --prd(PRD + orchestrator.yaml + registry.yaml + 主仓库 prototype/)
|
|
7
|
+
* / --arch(docs/architecture/ 迭代快照)
|
|
8
|
+
* / --changes <dir>(change 制品,缺省则整个 changes/)
|
|
9
|
+
* / --all(全部)
|
|
10
|
+
*
|
|
11
|
+
* 设计(v0.15 §68.3):
|
|
12
|
+
* 1. 白名单 git add:只 add 本次 target 对应目录,防卷走无关改动(复用 arch-merge 协作安全模式)
|
|
13
|
+
* 2. 脏文件检测:target 目录之外存在未提交文件 → 告警(不阻断,提示人工确认)
|
|
14
|
+
* 3. 写锁:.publish.lock(mkdir 原子语义,防并行 publish 冲突)
|
|
15
|
+
* 4. push 确认制:--push 显式开启;只推当前分支、不跨分支(push 是外向操作,强制确认)
|
|
16
|
+
* 5. --dry-run:只打印将执行的 git 动作,不落盘不 commit 不 push
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { execFileSync } from 'node:child_process';
|
|
20
|
+
import { existsSync, mkdirSync, readdirSync, rmdirSync } from 'node:fs';
|
|
21
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
22
|
+
|
|
23
|
+
const LOCK_PATH = '.publish.lock';
|
|
24
|
+
|
|
25
|
+
/** 解析 CLI 参数。 */
|
|
26
|
+
function parseArgv(argv) {
|
|
27
|
+
const parsed = { targets: [], push: false, dryRun: false, changeDir: null };
|
|
28
|
+
for (let i = 0; i < argv.length; i++) {
|
|
29
|
+
const a = argv[i];
|
|
30
|
+
if (a === '--push') parsed.push = true;
|
|
31
|
+
else if (a === '--dry-run') parsed.dryRun = true;
|
|
32
|
+
else if (a === '--prd') parsed.targets.push('prd');
|
|
33
|
+
else if (a === '--arch') parsed.targets.push('arch');
|
|
34
|
+
else if (a === '--changes') {
|
|
35
|
+
// 无目录 = 整个 changes/;带目录 = 单个 change(目录以 -- 开头则视为无目录,MAJOR 修复 v0.37.0)
|
|
36
|
+
parsed.targets.push('changes');
|
|
37
|
+
const next = argv[i + 1];
|
|
38
|
+
if (next && !next.startsWith('--')) parsed.changeDir = argv[++i];
|
|
39
|
+
} else if (a === '--all') parsed.targets.push('all');
|
|
40
|
+
else if (a === '-h' || a === '--help') parsed.help = true;
|
|
41
|
+
else {
|
|
42
|
+
console.error(`Unknown argument: ${a}`);
|
|
43
|
+
console.error('Usage: tf publish <--prd|--arch|--changes <dir>|--all> [--push] [--dry-run]');
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (parsed.targets.length === 0 && !parsed.help) {
|
|
48
|
+
console.error('No target specified.');
|
|
49
|
+
console.error('Usage: tf publish <--prd|--arch|--changes <dir>|--all> [--push] [--dry-run]');
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** git 执行封装(统一 encoding + 错误降级)。 */
|
|
56
|
+
function git(cwd, ...args) {
|
|
57
|
+
return execFileSync('git', args, { encoding: 'utf-8', cwd, stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isDir(p) {
|
|
61
|
+
try { return existsSync(p) && readdirSync(p).length >= 0; } catch { return false; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 收集本次 publish 的白名单路径(目录级)。
|
|
66
|
+
* 只 add 这些路径;target 目录之外的未提交改动一律不碰。
|
|
67
|
+
*/
|
|
68
|
+
function collectWhitelist(projectRoot, targets, changeDir) {
|
|
69
|
+
const paths = [];
|
|
70
|
+
const want = t => targets.includes('all') || targets.includes(t);
|
|
71
|
+
|
|
72
|
+
if (want('prd')) {
|
|
73
|
+
const prdDir = join(projectRoot, 'prd');
|
|
74
|
+
if (isDir(prdDir)) paths.push(prdDir);
|
|
75
|
+
// 主仓库内 prototype/(独立仓库场景由原型 worktree 命令管理,这里仅收集主仓库可见部分)
|
|
76
|
+
const protoDir = join(projectRoot, 'prototype');
|
|
77
|
+
if (isDir(protoDir)) paths.push(protoDir);
|
|
78
|
+
// 产品级编排状态
|
|
79
|
+
const teamflow = join(projectRoot, '.team-flow');
|
|
80
|
+
if (isDir(teamflow)) {
|
|
81
|
+
const registry = join(teamflow, 'registry.yaml');
|
|
82
|
+
if (existsSync(registry)) paths.push(registry);
|
|
83
|
+
const reqDir = join(teamflow, 'requirements');
|
|
84
|
+
if (isDir(reqDir)) {
|
|
85
|
+
for (const req of readdirSync(reqDir)) {
|
|
86
|
+
const oy = join(reqDir, req, 'orchestrator.yaml');
|
|
87
|
+
if (existsSync(oy)) paths.push(oy);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (want('arch')) {
|
|
94
|
+
const archDir = join(projectRoot, 'docs', 'architecture');
|
|
95
|
+
if (isDir(archDir)) paths.push(archDir);
|
|
96
|
+
// arch_baseline 豁免键(v0.37.0 §68.5):随架构快照一并同步,否则团队拉取后门禁误判存量(IMPORTANT 修复)
|
|
97
|
+
const archState = join(projectRoot, '.team-flow', 'arch-state.json');
|
|
98
|
+
if (existsSync(archState)) paths.push(archState);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (want('changes')) {
|
|
102
|
+
if (changeDir) {
|
|
103
|
+
const abs = isAbsolute(changeDir) ? changeDir : resolve(projectRoot, changeDir);
|
|
104
|
+
if (existsSync(abs)) paths.push(abs);
|
|
105
|
+
else console.warn(`[WARN] change 目录不存在:${abs}`);
|
|
106
|
+
} else {
|
|
107
|
+
const changesDir = join(projectRoot, 'changes');
|
|
108
|
+
if (isDir(changesDir)) paths.push(changesDir);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return paths;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 检测白名单之外的未提交/未跟踪文件(团队协作安全底线,仿 arch-merge detectUntouchedDirtyFiles)。
|
|
117
|
+
* 返回值:外部脏文件列表(含 staged + unstaged + untracked)。
|
|
118
|
+
*/
|
|
119
|
+
function detectOutsideDirty(projectRoot, whitelist) {
|
|
120
|
+
const relWhitelist = new Set(whitelist.map(p => {
|
|
121
|
+
const rel = p.replace(projectRoot + '/', '');
|
|
122
|
+
return rel.replace(/\/+$/, '');
|
|
123
|
+
}));
|
|
124
|
+
try {
|
|
125
|
+
const status = git(projectRoot, 'status', '--porcelain');
|
|
126
|
+
if (!status) return [];
|
|
127
|
+
return status.split('\n').filter(Boolean)
|
|
128
|
+
.map(line => {
|
|
129
|
+
// porcelain 前 2 字符是状态,后面是路径(引号包裹的可能带空格)
|
|
130
|
+
const raw = line.slice(3).replace(/^"|"$/g, '').replace(/\\ /g, ' ');
|
|
131
|
+
return raw;
|
|
132
|
+
})
|
|
133
|
+
.filter(p => {
|
|
134
|
+
if (!p) return false;
|
|
135
|
+
const norm = p.replace(/\/+$/, ''); // porcelain 目录级输出带尾斜杠,先去规范化
|
|
136
|
+
// 跳过 .publish.lock 自身(写锁目录)
|
|
137
|
+
if (norm === LOCK_PATH || norm.startsWith(LOCK_PATH + '/')) return false;
|
|
138
|
+
// 跳过 worktree 隔离区(gitignored,理论上不会出现,防御性跳过)
|
|
139
|
+
if (norm.startsWith('.worktrees/')) return false;
|
|
140
|
+
// 白名单判定(双向往返匹配):
|
|
141
|
+
// - norm 在白名单项内(norm === w 或 norm.startsWith(w + '/'))→ 豁免
|
|
142
|
+
// - 白名单项是 norm 的后代(w.startsWith(norm + '/'))→ 豁免
|
|
143
|
+
// (全新仓库 porcelain 对未跟踪内容显示目录级如 `?? docs/`,
|
|
144
|
+
// 其下白名单子目录 docs/architecture 已被覆盖,属正常提交范围)
|
|
145
|
+
for (const w of relWhitelist) {
|
|
146
|
+
if (norm === w || norm.startsWith(w + '/')) return false;
|
|
147
|
+
if (w.startsWith(norm + '/')) return false;
|
|
148
|
+
}
|
|
149
|
+
return true;
|
|
150
|
+
});
|
|
151
|
+
} catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function acquireLock(projectRoot) {
|
|
157
|
+
const lockPath = join(projectRoot, LOCK_PATH);
|
|
158
|
+
if (existsSync(lockPath)) {
|
|
159
|
+
throw new Error(`another publish in progress (${LOCK_PATH} exists) — 串行化提交,稍后重试`);
|
|
160
|
+
}
|
|
161
|
+
mkdirSync(lockPath);
|
|
162
|
+
return lockPath;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function releaseLock(lockPath) {
|
|
166
|
+
try { rmdirSync(lockPath); } catch { /* ignore */ }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function describeTargets(parsed) {
|
|
170
|
+
const t = parsed.targets.includes('all') ? 'all' : parsed.targets.join('+');
|
|
171
|
+
return parsed.targets.includes('changes') && parsed.changeDir ? `${t}:${parsed.changeDir}` : t;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* CLI 入口(export 供 team-flow.mjs 注册)。
|
|
176
|
+
*/
|
|
177
|
+
export async function run(args = []) {
|
|
178
|
+
const parsed = parseArgv(args);
|
|
179
|
+
if (parsed.help) {
|
|
180
|
+
console.log(`tf publish — 阶段产物白名单提交 + 可选推送(v0.37.0 §68.3)
|
|
181
|
+
|
|
182
|
+
用法:tf publish <target> [--push] [--dry-run]
|
|
183
|
+
--prd 提交 PRD + orchestrator.yaml + registry.yaml + 主仓库 prototype/
|
|
184
|
+
--arch 提交 docs/architecture/(迭代快照)
|
|
185
|
+
--changes 提交整个 changes/(或 --changes <change-dir> 指定单个 change)
|
|
186
|
+
--all 提交全部上述
|
|
187
|
+
--push 同时推送到远程当前分支(默认只提交不推送)
|
|
188
|
+
--dry-run 只打印将执行的 git 动作,不落盘不 commit`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const projectRoot = git(process.cwd(), 'rev-parse', '--show-toplevel');
|
|
193
|
+
const whitelist = collectWhitelist(projectRoot, parsed.targets, parsed.changeDir);
|
|
194
|
+
|
|
195
|
+
if (whitelist.length === 0) {
|
|
196
|
+
console.log('ℹ️ 没有可提交的阶段产物(白名单为空)。');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 脏文件检测(target 之外)→ 告警不阻断
|
|
201
|
+
const outsideDirty = detectOutsideDirty(projectRoot, whitelist);
|
|
202
|
+
if (outsideDirty.length > 0) {
|
|
203
|
+
console.warn(`[WARN] 白名单之外存在 ${outsideDirty.length} 个未提交文件(不提交):`);
|
|
204
|
+
for (const f of outsideDirty.slice(0, 5)) console.warn(` - ${f}`);
|
|
205
|
+
if (outsideDirty.length > 5) console.warn(` ... 等 ${outsideDirty.length} 个`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const targetDesc = describeTargets(parsed);
|
|
209
|
+
const commitMsg = `tf publish: ${targetDesc} — sync stage artifacts`;
|
|
210
|
+
|
|
211
|
+
if (parsed.dryRun) {
|
|
212
|
+
console.log('[DRY-RUN] Would execute:');
|
|
213
|
+
console.log(` git add ${whitelist.map(p => p.replace(projectRoot + '/', '')).join(' ')}`);
|
|
214
|
+
console.log(` git commit -m "${commitMsg}"`);
|
|
215
|
+
if (parsed.push) {
|
|
216
|
+
const branch = git(projectRoot, 'branch', '--show-current');
|
|
217
|
+
console.log(` git push origin ${branch}`);
|
|
218
|
+
}
|
|
219
|
+
return { published: false, dryRun: true };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const lockPath = acquireLock(projectRoot);
|
|
223
|
+
try {
|
|
224
|
+
// 白名单 git add(只 add 目标目录)
|
|
225
|
+
for (const p of whitelist) {
|
|
226
|
+
const rel = p.replace(projectRoot + '/', '');
|
|
227
|
+
execFileSync('git', ['add', rel], { cwd: projectRoot, stdio: 'pipe' });
|
|
228
|
+
}
|
|
229
|
+
// commit(nothing to commit 可接受)
|
|
230
|
+
try {
|
|
231
|
+
execFileSync('git', ['commit', '-m', commitMsg], { cwd: projectRoot, stdio: 'pipe' });
|
|
232
|
+
console.log(`✅ committed: ${commitMsg}`);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
// git 空提交消息在 stdout(execFileSync 失败时 stderr 常为空 Buffer,需先排除空值)
|
|
235
|
+
const msg = (e.stderr && e.stderr.toString()) || (e.stdout && e.stdout.toString()) || '';
|
|
236
|
+
// git 空提交两种提示:工作树干净 / 有未跟踪但无 staged 变更
|
|
237
|
+
if (msg.includes('nothing to commit') || msg.includes('nothing added to commit')) {
|
|
238
|
+
console.log('ℹ️ 没有变更需要提交。');
|
|
239
|
+
} else {
|
|
240
|
+
throw new Error(`git commit failed: ${msg}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// push(确认制)
|
|
244
|
+
if (parsed.push) {
|
|
245
|
+
const branch = git(projectRoot, 'branch', '--show-current');
|
|
246
|
+
console.log(`↗️ pushing to origin/${branch} ...`);
|
|
247
|
+
execFileSync('git', ['push', 'origin', branch], { cwd: projectRoot, stdio: 'inherit' });
|
|
248
|
+
console.log('✅ pushed.');
|
|
249
|
+
}
|
|
250
|
+
return { published: true, targets: parsed.targets, pushed: parsed.push };
|
|
251
|
+
} finally {
|
|
252
|
+
releaseLock(lockPath);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { readFileSync, writeFileSync, existsSync, cpSync, mkdirSync } from 'node:fs';
|
|
18
|
-
import { join, basename, relative } from 'node:path';
|
|
18
|
+
import { join, basename, relative, sep, resolve } from 'node:path';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* 解析 CLI 参数数组为结构化对象
|
|
@@ -36,6 +36,45 @@ function parseArgv(argv) {
|
|
|
36
36
|
return parsed;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* 推导 change 所属 PRD 版本(v0.37.0 §68.5)。
|
|
41
|
+
* 优先级:change-brief.md 的 upstream_plan_ref → change 目录名 v{N}- 前缀。
|
|
42
|
+
* @returns {string|null} 如 'v2';无法推导返回 null
|
|
43
|
+
*/
|
|
44
|
+
function derivePrdVersion(changeDir) {
|
|
45
|
+
const briefPath = join(changeDir, 'change-brief.md');
|
|
46
|
+
try {
|
|
47
|
+
if (existsSync(briefPath)) {
|
|
48
|
+
const content = readFileSync(briefPath, 'utf-8');
|
|
49
|
+
const m = content.match(/upstream_plan_ref:\s*prd\/(v\d+)\//);
|
|
50
|
+
if (m) return m[1];
|
|
51
|
+
}
|
|
52
|
+
} catch { /* ignore */ }
|
|
53
|
+
const m = basename(changeDir).match(/^v(\d+)-/);
|
|
54
|
+
if (m) return `v${m[1]}`;
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 解析原型目录(v0.37.0 §68.5 制品链路径版本化):
|
|
60
|
+
* --prototype-dir 显式指定优先;否则从 change 推导 PRD 版本 → 定位版本 worktree 的 prototype/。
|
|
61
|
+
* worktree 不存在时回退主仓库 prototype/ 并提示。
|
|
62
|
+
*/
|
|
63
|
+
function resolvePrototypeDir(changeDir, prototypeDirArg) {
|
|
64
|
+
if (prototypeDirArg) return prototypeDirArg;
|
|
65
|
+
const version = derivePrdVersion(changeDir);
|
|
66
|
+
if (!version) return 'prototype'; // 无法推导版本 → 回退主仓库 prototype/
|
|
67
|
+
const abs = resolve(changeDir);
|
|
68
|
+
const changesIdx = abs.lastIndexOf(sep + 'changes' + sep);
|
|
69
|
+
const workspaceRoot = changesIdx > 0 ? abs.slice(0, changesIdx) : process.cwd();
|
|
70
|
+
const branchName = `prd-${version}`;
|
|
71
|
+
const wt = join(workspaceRoot, '.worktrees', branchName, 'prototype');
|
|
72
|
+
if (existsSync(join(wt, '.git'))) return wt;
|
|
73
|
+
const fallback = join(workspaceRoot, 'prototype');
|
|
74
|
+
console.log(`ℹ️ 原型版本 worktree 不存在(${wt}),回退 ${fallback}。原型独立仓库场景请先 \`tf prototype branch ${branchName}\`。`);
|
|
75
|
+
return fallback;
|
|
76
|
+
}
|
|
77
|
+
|
|
39
78
|
/**
|
|
40
79
|
* 执行 prototype-sync 合并
|
|
41
80
|
* @param {string[]|object} args - CLI 参数数组(从 tf 框架传入)或已解析的对象
|
|
@@ -49,7 +88,7 @@ export function run(args = {}) {
|
|
|
49
88
|
|
|
50
89
|
const changeDir = args._?.[0] || args.changeDir;
|
|
51
90
|
const sourcePath = args.source;
|
|
52
|
-
const prototypeDir = args.prototypeDir
|
|
91
|
+
const prototypeDir = resolvePrototypeDir(changeDir, args.prototypeDir);
|
|
53
92
|
|
|
54
93
|
if (!changeDir) {
|
|
55
94
|
console.error('Usage: tf prototype-sync <change-dir> [--source <ux-delta-path>] [--prototype-dir <path>]');
|
package/scripts/team-flow.mjs
CHANGED
|
@@ -34,6 +34,8 @@ const COMMANDS = {
|
|
|
34
34
|
'install-qoder': () => import('./lib/cmd-install-qoder.mjs'),
|
|
35
35
|
'install-zcode': () => import('./lib/cmd-install-zcode.mjs'),
|
|
36
36
|
'prototype-sync': () => import('./lib/prototype-sync.mjs'),
|
|
37
|
+
prototype: () => import('./lib/cmd-prototype.mjs'),
|
|
38
|
+
publish: () => import('./lib/cmd-publish.mjs'),
|
|
37
39
|
'arch-merge': () => import('./lib/arch-merge.mjs'),
|
|
38
40
|
arch: () => import('./lib/cmd-arch.mjs'),
|
|
39
41
|
'test-merge': () => import('./lib/test-merge.mjs'),
|
|
@@ -53,6 +55,12 @@ Commands:
|
|
|
53
55
|
sync <change-dir> Merge delta specs into main specs
|
|
54
56
|
prototype-sync <change-dir> [--source <path>] [--prototype-dir <path>]
|
|
55
57
|
Merge UX delta into global prototype/ + design-system.md
|
|
58
|
+
publish <--prd|--arch|--changes <dir>|--all> [--push] [--dry-run]
|
|
59
|
+
Commit stage artifacts (whitelist) + optional push (v0.37.0 §68.3)
|
|
60
|
+
prototype branch <prd-vN>
|
|
61
|
+
Create/reuse prototype version worktree (v0.37.0 §68.4)
|
|
62
|
+
prototype deisolate <prd-vN> [--merge] [--clean]
|
|
63
|
+
Version wrap-up: merge prototype branch back + clean worktree
|
|
56
64
|
arch init [--mode reconstruction|design] [--baseline-ref <prd/vN/>]
|
|
57
65
|
Stamp project-level arch_baseline into .team-flow/arch-state.json (v0.35.0 §59.4)
|
|
58
66
|
arch show Show current project architecture baseline state
|
|
@@ -72,6 +72,15 @@
|
|
|
72
72
|
|
|
73
73
|
逆向重建工具:`workflow-bootstrap` 的 recon-probe.sh(--ddl-out,路径 `${CLAUDE_PLUGIN_ROOT}/skills/workflow-bootstrap/scripts/recon-probe.sh`)+ codebase-recon-analyst + 新增 `arch-reverse-analyst`(v0.14 §63.2)。
|
|
74
74
|
|
|
75
|
+
## 阶段产物同步门禁点(v0.37.0 §68.2 G2)
|
|
76
|
+
|
|
77
|
+
ARCH 完成(评审 PASS 或 skip 已物化)后,**阻塞确认**(AskUserQuestion)是否同步阶段产物(团队协作:架构决策是实施依据):
|
|
78
|
+
- **A 提交并推送**:`tf publish --arch --push`
|
|
79
|
+
- **B 仅提交不推送**:`tf publish --arch`
|
|
80
|
+
- **C 暂不同步**:继续 S4,后续可补
|
|
81
|
+
|
|
82
|
+
同步对象:`docs/architecture/iterations/vN/`(架构快照)+ 评审 verdict(含首轮 `tf arch init` 打戳产物 `.team-flow/arch-state.json`)。
|
|
83
|
+
|
|
75
84
|
## 完成条件
|
|
76
85
|
|
|
77
86
|
- `docs/architecture/iterations/vN/architecture.md` 已产出(6 产物,provenance 标注)
|
|
@@ -43,6 +43,13 @@ Branch/worktree preflight before ANY implementation edit (mandatory — do not s
|
|
|
43
43
|
aggregated `<workspace>/.worktrees/<change>/` directory — and make all implementation
|
|
44
44
|
edits there.
|
|
45
45
|
|
|
46
|
+
### UI Contract Prototype Location (v0.37.0 §68.5)
|
|
47
|
+
|
|
48
|
+
When implementing UI tasks, the UI contract source is the **prototype version worktree**, not the change worktree's prototype copy:
|
|
49
|
+
- **定位**:读 `change-brief.md` 的 `upstream_plan_ref: prd/vN/plan.md`(或 change 目录名 `v{N}-` 前缀)→ PRD 版本 vN → 原型 worktree = `<workspace>/.worktrees/prd-vN/prototype/`
|
|
50
|
+
- **回退**:该 worktree 不存在时用 `<workspace>/prototype/`,并提示 `tf prototype branch <prd-vN>` 创建
|
|
51
|
+
- **引用**:execution-contract / design.md 中引用的原型页面(`prototype/pages/<page>.html` 等)以此为基准定位;**禁止**引用主干上其他版本的旧原型
|
|
52
|
+
|
|
46
53
|
## Core Laws
|
|
47
54
|
|
|
48
55
|
### Law 0: State Field Boundary (v0.30.0)
|
|
@@ -36,6 +36,19 @@ Before finalizing:
|
|
|
36
36
|
|
|
37
37
|
Must make obvious: approved behavior, out-of-scope, constraints, batches, test obligations, review gates, and conditions that force a rewind to planning. Prefer compression over repeating planning details.
|
|
38
38
|
|
|
39
|
+
## UI UX Delta Prototype Reference (v0.37.0 §68.5)
|
|
40
|
+
|
|
41
|
+
When the change involves UI (design.md has a `## UI Contract` section), the execution-contract's `## UX 增量` section (consumed by `tf prototype-sync`) must **state the prototype version worktree path** in its first line:
|
|
42
|
+
|
|
43
|
+
```markdown
|
|
44
|
+
## UX 增量
|
|
45
|
+
原型目标:<workspace>/.worktrees/prd-vN/prototype/(PRD 版本 vN,从 change-brief `upstream_plan_ref` 推导)
|
|
46
|
+
[ADD] pages/xxx.html
|
|
47
|
+
[MODIFY] pages/yyy.html
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`[ADD]/[MODIFY]` relative paths stay prototype-root-relative; the stated worktree is what `prototype-sync` resolves to when `--prototype-dir` is absent (v0.37.0 §68.5 制品链路径版本化).
|
|
51
|
+
|
|
39
52
|
## Test Matrix Generation (v0.12 §42, v0.13 §52 B1 强制化)
|
|
40
53
|
|
|
41
54
|
`test-matrix.md` 是 `execution-contract.md` 的**附属产物**(不是独立第 6 核心产物),在 contract 的 `## Test Matrix` 段引用。
|
package/skills/e2e/SKILL.md
CHANGED
|
@@ -52,5 +52,7 @@ user-invocable: true
|
|
|
52
52
|
## 复用机制
|
|
53
53
|
单一 `e2e/` + `playwright.config.ts` `projects`(baseURL):`prototype` project → http-server 托管 HTML;`integration` project → 真实应用。脚本一致,仅切地址 + 少量选择器/断言适配(高重叠复用,非零触碰;原型绿 ≠ 集成绿)。
|
|
54
54
|
|
|
55
|
+
**原型期托管(v0.37.0 §68.5)**:`prototype` project 托管的原型 = 对应 PRD 版本 worktree 的 `prototype/`(`<workspace>/.worktrees/prd-vN/prototype/`,版本从 change-brief `upstream_plan_ref` 推导);baseURL 指向该 worktree 的 http-server,**不指向主干旧版本原型**。该 worktree 缺失时回退 `<workspace>/prototype/` 并提示 `tf prototype branch <prd-vN>` 创建(与 prototype-sync 回退语义一致)。
|
|
56
|
+
|
|
55
57
|
## 与 release-archivist 衔接
|
|
56
58
|
e2e 报告 + 4 级制品比对(L1 存在 / L2 实质 / L3 接线 / L4 数据流)作为 closing 验收 gate 的验证维度,由 `release-archivist` 接入(`ce-proof` 是 Proof 编辑器,与此无关)。
|
|
@@ -87,6 +87,7 @@ prototype/
|
|
|
87
87
|
## 分支约定
|
|
88
88
|
- 原型随 **PRD 当前版本分支**维护(如 `prd-v2` 分支的 `prototype/` = v2 产品原型)。
|
|
89
89
|
- change 引用其所在 PRD 版本分支的 `prototype/`。
|
|
90
|
+
- **版本 worktree 管理(v0.37.0 §68.4)**:原型为独立仓库(有远程),版本分支的 worktree 由 `tf prototype branch <prd-vN>` 创建/复用(隔离 + 团队拉取入口);版本收尾(change_dag 该版本全部 closing)由 `tf prototype deisolate <prd-vN> --merge --clean` merge 回主干 + 清理(方案 A:merge 时机 = 版本收尾)。原型 worktree 布局 `<workspace>/.worktrees/prd-vN/prototype/`(与代码实施 `.worktrees/` 统一隔离区)。
|
|
90
91
|
|
|
91
92
|
## prototype-sync(change 完成回写,release-archivist 自动触发,v0.24.0 升级)
|
|
92
93
|
|
|
@@ -203,6 +203,15 @@ tf solutions capture \
|
|
|
203
203
|
|
|
204
204
|
This check is advisory — skip silently if no learnable moments are detected.
|
|
205
205
|
|
|
206
|
+
### 阶段产物同步门禁点(v0.37.0 §68.2 G5)
|
|
207
|
+
|
|
208
|
+
回写链(arch-merge → prototype-sync → test-merge → compound promotion)全部完成后、`tf state transition closing` 之前,**阻塞确认**(AskUserQuestion)是否同步 change 实施结果(团队协作:落地结果是团队最需要看的内容):
|
|
209
|
+
- **A 提交并推送**:`tf publish --changes <change-dir> --push`
|
|
210
|
+
- **B 仅提交不推送**:`tf publish --changes <change-dir>`(arch-merge/test-merge 已自动 commit,此步补 push)
|
|
211
|
+
- **C 暂不同步**:继续 closing,后续可补
|
|
212
|
+
|
|
213
|
+
同步对象:实施代码 `changes/<change-dir>/` + arch-merge 回写(docs/architecture/)+ test-ledger(docs/test-ledger/)。**原型独立仓库场景**:若本 change 有 UX 增量回写原型,push 对应 `prd-vN` 分支(`tf prototype branch <prd-vN>` 确认/创建 worktree)。
|
|
214
|
+
|
|
206
215
|
### Worktree Deisolation (v0.35.0) — advisory
|
|
207
216
|
|
|
208
217
|
After compound promotion, check if worktree isolation exists for this change:
|
|
@@ -11,15 +11,19 @@ Create or refine planning artifacts when the change has moved beyond exploration
|
|
|
11
11
|
|
|
12
12
|
Read `.team-flow.yaml` (especially `dp_0_decisions`, `dp_0_confirmed`) and any existing planning artifacts. If `dp_0_confirmed` is not `true`, stop and route back to `workflow-start` for DP-0.
|
|
13
13
|
|
|
14
|
-
### Global Prototype Reading (v0.5)
|
|
14
|
+
### Global Prototype Reading (v0.5, v0.37.0 版本化)
|
|
15
15
|
|
|
16
|
-
Before generating artifacts,
|
|
17
|
-
1.
|
|
18
|
-
2.
|
|
19
|
-
3. Read `prototype/
|
|
20
|
-
4.
|
|
16
|
+
Before generating artifacts, **resolve the PRD version and locate the prototype worktree**:
|
|
17
|
+
1. **版本推导(v0.37.0 §68.5)**:读 `change-brief.md` 的 `upstream_plan_ref: prd/vN/plan.md`(无 brief 时从 change 目录名 `v{N}-` 前缀推导)→ PRD 版本 vN
|
|
18
|
+
2. **原型定位**:原型为独立仓库(有远程),版本 worktree = `<workspace>/.worktrees/prd-vN/prototype/`(该 worktree 不存在时回退 `<workspace>/prototype/` 并提示"原型应位于版本 worktree,`tf prototype branch <prd-vN>` 创建")
|
|
19
|
+
3. Read `prototype/flow.md` if it exists — this defines page navigation and user flows
|
|
20
|
+
4. List `prototype/pages/` directory — these are the UI contract pages
|
|
21
|
+
5. Read `prototype/design-system.md` if it exists — component and token definitions
|
|
22
|
+
6. Store as UI contract context for design.md and tasks.md generation
|
|
21
23
|
|
|
22
|
-
If
|
|
24
|
+
If no prototype exists or it is empty, skip silently — not all projects have prototypes.
|
|
25
|
+
|
|
26
|
+
**制品引用约定(v0.37.0 §68.5)**:design.md / tasks.md 中引用原型保留 `prototype/<rel>` 相对写法(可读性),但 `## UI Contract` / `**Prototype:**` 段必须**声明本 change 所属 PRD 版本 vN + 原型 worktree 路径**——实施阶段(build-executor)据此定位同一份原型,避免指向主干旧版本或空目录。
|
|
23
27
|
|
|
24
28
|
### Solutions Index (v0.5)
|
|
25
29
|
|
|
@@ -82,6 +82,15 @@ PRD草稿生成后:
|
|
|
82
82
|
|
|
83
83
|
设置 `frozen_downstream`(见 state-model.md「双层冻结」)。冻结后,**迭代内变更不升版**,在 vN 内修订 + 记录决策/变更履历。
|
|
84
84
|
|
|
85
|
+
### 4.5 阶段产物同步门禁点(v0.37.0 §68.2 G1)
|
|
86
|
+
|
|
87
|
+
S2 冻结后,**阻塞确认**(AskUserQuestion)是否同步阶段产物(团队协作:PRD+原型是需求讨论基线):
|
|
88
|
+
- **A 提交并推送**:`tf publish --prd --push`;原型为独立仓库时先 `tf prototype branch <prd-vN>` 确认/创建版本 worktree(迭代完成 push `prd-vN` 分支供团队查看者 `tf prototype branch <prd-vN>` 一键拉取)
|
|
89
|
+
- **B 仅提交不推送**:`tf publish --prd`
|
|
90
|
+
- **C 暂不同步**:继续 S3,后续可补
|
|
91
|
+
|
|
92
|
+
同步对象:`prd/vN/` + 原型(push `prd-vN` 分支)+ `.team-flow/`(orchestrator.yaml / registry.yaml)。
|
|
93
|
+
|
|
85
94
|
### 5. 复利捕获
|
|
86
95
|
|
|
87
96
|
检测可复利时刻(需求矛盾、原型返工、自动评审发现不一致并修正等)。每次自动评审发现不一致并修正后触发:
|
|
@@ -137,6 +137,15 @@ Change C1 已就绪,运行 /workflow-start 进入变更级流程。
|
|
|
137
137
|
执行顺序建议:C1 → (C2 ∥ C3) → C4(C2/C3 可并行)
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
+
### 5.5 阶段产物同步门禁点(v0.37.0 §68.2 G3)
|
|
141
|
+
|
|
142
|
+
S4 分发完成(orchestrator 结束)后,**阻塞确认**(AskUserQuestion)是否同步阶段产物(团队协作:成员领取任务的前提):
|
|
143
|
+
- **A 提交并推送**:`tf publish --changes --push`
|
|
144
|
+
- **B 仅提交不推送**:`tf publish --changes`
|
|
145
|
+
- **C 暂不同步**:结束编排,后续可补
|
|
146
|
+
|
|
147
|
+
同步对象:`changes/*/`(脚手架 + change-brief.md)+ `.team-flow/`(change_dag / orchestrator.yaml)= 任务分发(成员领取)。
|
|
148
|
+
|
|
140
149
|
## 完成条件
|
|
141
150
|
|
|
142
151
|
- change-split-auditor 审计 verdict = PASS(必选门禁已通过,含 v0.9 D5 拆分维度合规)
|
|
@@ -62,6 +62,7 @@ Validate mode against artifact content. If hotfix/tweak criteria not met → upg
|
|
|
62
62
|
|
|
63
63
|
- **记录 agentId**:dispatch 后立即记录子代理 ID;修复一律 SendMessage 恢复原子代理(禁启动新子代理,见 v0.29.0 §37)
|
|
64
64
|
- **上下文水位例外(v0.35.0)**:当原子代理 transcript 超过 2MB 或累计 token 消耗超过 200 万时,主代理可以启动新代理替代恢复。新代理必须接收完整交接文档(当前进度、已完成 wave 列表、未解决问题、artifact 位置、已知 blocker),以保证连续性。此为 SendMessage 恢复原则的唯一补充例外
|
|
65
|
+
- **build-executor 交互轮次轮转(v0.36.4)**:主代理通过 SendMessage 恢复 build-executor 时,累计交互轮次。当累计 ≥6 轮(~3 wave 的典型交互量:放行→实施→审查→FAIL→修→复评)时,checkpoint 当前进度并启动新 build-executor 子代理。交接文档必须包含:已完成 waves 及 receipt 状态、累积技术决策(如命名约定、架构选型)、worktree commit SHA、剩余 waves 及依赖关系。此规则独立于上下文水位例外——两者取先触发者
|
|
65
66
|
- **返回即验证(validation gate)**:子代理返回产物后**立即** `tf validate <change-dir>`;FAIL → SendMessage 回**原**子代理修复,通过后才能继续/转换。把格式失败拦截在返回时,而非状态转换时(避免浪费整次执行后再失败)。**build-executor 附加验证(v0.13 §52 B3,SDD/full 模式)**:返回的 diff 中必须包含测试文件(src/test/ 或项目测试目录);零测试文件的实现返回一律 BLOCK,SendMessage 回原子代理按 test-matrix.md / TDD Iron Law 补齐——不得以"手动验证"替代(C1-domain-policy 事件教训)。例外:已显式记录 `test_matrix_skipped=true` + 理由的 change(纯文档/配置类)不要求测试文件
|
|
66
67
|
- **结果协议**:子代理终态须标注 `FINAL VERDICT: <DONE|BLOCKED|FAIL>`(审查类用 PASS/PASS_WITH_WARNINGS/FAIL);**SendMessage 报告为权威结果**,task-notification.result 仅内部元数据
|
|
67
68
|
- **子代理状态边界**:子代理 MUST NOT 修改 `.team-flow.yaml` 的 `state`/`workflow` 核心字段(状态转换是主代理专有职责,经 `tf state transition` 执行),只写自己的 `dp_N_*` 决策字段
|
|
@@ -92,12 +93,23 @@ Guard: `... check <dir> specifying bridging --json` → fail = BLOCK. Artifacts
|
|
|
92
93
|
### Route to build-executor (dispatch sub-agent)
|
|
93
94
|
Contract exists and approved, contract matches artifacts. Include `DP-4: 执行模式选择`: propose waves, run `tf execution recommend <change-dir> [--wave ...]`, show the user every available mode plus evidence and the recommendation, then obtain a clear selection. The command saves a current receipt; before the first implementation edit, `build-executor` must run `tf execution plan <change-dir> --mode <selected> --confirm ...` (and `--acknowledge-recommendation` when the selected mode differs from the recommendation) using matching artifacts, contract, and waves, then `tf execution show <change-dir> --json`; report the saved revision, selected mode, recommendation alignment, ordered waves, and actual concurrent-dispatch capability. A revision must repeat recommend and confirmation. Do not transition to `executing` until `show` reports `current: true`; then run `... check <dir> approved-for-build executing --json` → fail = BLOCK. **v0.13 §49 门禁前移**:该 guard 含 `test-matrix-ready` 维度——full 模式非存量 change 必须"带着矩阵开工"(矩阵存在非空 OR 显式 skip 附理由);FAIL 时回 bridging 让 contract-builder 补矩阵,或按指引显式 skip,禁止绕过。
|
|
94
95
|
|
|
96
|
+
### 阶段产物同步门禁点(v0.37.0 §68.2 G4)
|
|
97
|
+
|
|
98
|
+
契约批准(DP-3)后、进入实施(DP-4)前,**阻塞确认**(AskUserQuestion)是否同步 change 规划制品(团队协作:契约是实施授权):
|
|
99
|
+
- **A 提交并推送**:`tf publish --changes <change-dir> --push`
|
|
100
|
+
- **B 仅提交不推送**:`tf publish --changes <change-dir>`
|
|
101
|
+
- **C 暂不同步**:直接进入实施,后续可补
|
|
102
|
+
|
|
103
|
+
同步对象:change 规划制品 4+1(proposal / specs / design / tasks / execution-contract)+ `architecture/`。
|
|
104
|
+
|
|
95
105
|
### Route to bug-investigator
|
|
96
106
|
Execution hit blockage: test failure, unexpected behavior, build error, task cannot proceed. After debugging, route back to build-executor.
|
|
97
107
|
|
|
98
108
|
### Route to code-reviewer
|
|
99
109
|
The current planned wave is implemented and ready for spec-compliance + code-quality verification. A reviewer must write an `tf execution review <change-dir> --wave <id> --base <sha> --head <sha> --report <path> --verdict <pass|fail>` receipt before any dependent wave or closing transition.
|
|
100
110
|
|
|
111
|
+
**逐 wave 审查纪律(v0.36.4)**:每次 code-reviewer dispatch 只审查**一个** wave。禁止将多个 wave 合并为一次审查(vrm4teamflow C1 事件:W1-W4 合并审查导致设计约定类问题在 W4 才发现,修复范围扩大到 8 个 batch)。parallel wave 内的多个 task 作为整体审查一次(receipt 是 wave 级别),但审查 prompt 必须包含每个 task 的独立 diff。
|
|
112
|
+
|
|
101
113
|
### Route to release-archivist (dispatch sub-agent)
|
|
102
114
|
Guard: `... check <dir> executing closing --json` → fail = BLOCK. Implementation complete, verification complete/nearly complete. Include `DP-7: 归档确认`.
|
|
103
115
|
|