@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.
- package/.claude/always/phase-guard.md +1 -1
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.cursor-plugin/plugin.json +2 -2
- package/.github/plugin/marketplace.json +2 -2
- package/AGENTS.md +10 -4
- package/CHANGELOG.md +88 -0
- package/GEMINI.md +1 -1
- package/HANDOFF.md +4 -4
- package/INSTALL.md +1 -1
- package/README.md +5 -5
- package/agents/architecture-design.md +1 -0
- package/agents/build-executor.md +81 -0
- package/agents/contract-builder.md +78 -0
- package/agents/cross-change-consistency-checker.md +1 -1
- package/agents/need-explorer.md +67 -0
- package/agents/release-archivist.md +82 -0
- package/agents/spec-writer.md +83 -0
- 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 +2 -2
- package/plugin.json +2 -2
- package/scripts/ensure-branch.mjs +200 -42
- package/scripts/guard/checks/test-matrix-complete.mjs +67 -0
- package/scripts/guard/guard.mjs +5 -1
- package/scripts/lib/cmd-doctor.mjs +40 -1
- package/scripts/lib/cmd-state.mjs +33 -13
- package/scripts/lib/hash.mjs +11 -0
- package/scripts/lib/state-loader.mjs +34 -1
- package/scripts/lib/test-matrix-export.mjs +231 -0
- package/scripts/lib/test-merge.mjs +540 -0
- package/scripts/team-flow.mjs +6 -0
- package/skills/build-executor/SKILL.md +14 -4
- package/skills/build-executor/implementer-prompt.md +38 -3
- package/skills/code-reviewer/SKILL.md +28 -1
- package/skills/code-reviewer/code-reviewer-prompt.md +10 -0
- package/skills/contract-builder/SKILL.md +78 -0
- package/skills/need-explorer/SKILL.md +2 -0
- package/skills/release-archivist/SKILL.md +37 -2
- package/skills/spec-writer/SKILL.md +5 -1
- package/skills/test-strategy/SKILL.md +70 -0
- package/skills/test-strategy/references/adversarial-patterns.md +0 -0
- package/skills/test-strategy/references/complexity-grading.md +137 -0
- package/skills/test-strategy/references/design-methods-detail.md +183 -0
- package/skills/workflow-orchestrator/references/s1-path-router.md +4 -0
- package/skills/workflow-start/SKILL.md +29 -15
- package/skills/workflow-start/references/routing-rules.md +29 -6
- package/tests/lib/cmd-install-workbuddy.test.mjs +1 -1
- 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.
|
|
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)
|
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). 17 skills, one install.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.31.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.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.
|
|
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.
|
|
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.
|
|
4
|
-
"description": "Unified plugin (23 skills +
|
|
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.
|
|
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).
|
|
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
|
|
11
|
-
//
|
|
12
|
-
//
|
|
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
|
|
16
|
-
|
|
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 (!
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
}
|
package/scripts/guard/guard.mjs
CHANGED
|
@@ -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 = [];
|