@ifrankwang/openspec-agents 0.134.0 → 0.136.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/.mcp-server/cli.mjs +107 -20
- package/README.md +15 -0
- package/assets/skills/openspec-standard/SKILL.md +28 -0
- package/assets/skills/openspec-standard/templates/openspec/config.yaml +39 -0
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/README.md +79 -0
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/schema.yaml +469 -0
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/clarify.md +55 -0
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/design.md +31 -0
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/proposal.md +32 -0
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/spec.md +8 -0
- package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/tasks.md +41 -0
- package/package.json +1 -1
- package/src/adapters/mcp-common/server.ts +1 -1
- package/src/core/git.ts +143 -20
package/.mcp-server/cli.mjs
CHANGED
|
@@ -4056,13 +4056,14 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
4056
4056
|
if (!malformedIPLiteral) {
|
|
4057
4057
|
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
|
|
4058
4058
|
}
|
|
4059
|
-
if (
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4064
|
-
}
|
|
4059
|
+
if (uri.indexOf("%") !== -1 && parsed.host !== undefined && !malformedIPLiteral) {
|
|
4060
|
+
let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4061
|
+
if (!isIP) {
|
|
4062
|
+
host = normalizePercentEncoding(host.toLowerCase());
|
|
4065
4063
|
}
|
|
4064
|
+
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4065
|
+
}
|
|
4066
|
+
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4066
4067
|
if (parsed.path) {
|
|
4067
4068
|
parsed.path = normalizePathEncoding(parsed.path);
|
|
4068
4069
|
}
|
|
@@ -21715,6 +21716,45 @@ async function markTaskGroupCheckboxesComplete(worktree, changeId, taskGroupId)
|
|
|
21715
21716
|
throw new Error(`git commit tasks.md 失败:${commitResult.stderr}`);
|
|
21716
21717
|
}
|
|
21717
21718
|
}
|
|
21719
|
+
function unquotePath(p) {
|
|
21720
|
+
const t = p.trim();
|
|
21721
|
+
return t.startsWith('"') && t.endsWith('"') && t.length >= 2 ? t.slice(1, -1) : t;
|
|
21722
|
+
}
|
|
21723
|
+
function parseStatusEntries(out) {
|
|
21724
|
+
const entries = [];
|
|
21725
|
+
for (const raw of out.split(`
|
|
21726
|
+
`)) {
|
|
21727
|
+
if (raw.length < 4)
|
|
21728
|
+
continue;
|
|
21729
|
+
let line = raw;
|
|
21730
|
+
if (line[2] !== " ") {
|
|
21731
|
+
if (line[1] !== " ")
|
|
21732
|
+
continue;
|
|
21733
|
+
line = ` ${line}`;
|
|
21734
|
+
}
|
|
21735
|
+
if (line[0] === " " && line[1] === " ")
|
|
21736
|
+
continue;
|
|
21737
|
+
const pathPart = line.slice(3);
|
|
21738
|
+
const arrow = pathPart.indexOf(" -> ");
|
|
21739
|
+
const paths = arrow >= 0 ? [unquotePath(pathPart.slice(0, arrow)), unquotePath(pathPart.slice(arrow + 4))] : [unquotePath(pathPart)];
|
|
21740
|
+
if (paths.every((p) => p !== ""))
|
|
21741
|
+
entries.push({ x: line[0], y: line[1], paths });
|
|
21742
|
+
}
|
|
21743
|
+
return entries;
|
|
21744
|
+
}
|
|
21745
|
+
function parseDiffNameOnly(out) {
|
|
21746
|
+
return out.split(`
|
|
21747
|
+
`).map(unquotePath).filter(Boolean);
|
|
21748
|
+
}
|
|
21749
|
+
function pathsOverlap(a, b) {
|
|
21750
|
+
const na = a.replace(/\/+$/, "");
|
|
21751
|
+
const nb = b.replace(/\/+$/, "");
|
|
21752
|
+
if (!na || !nb)
|
|
21753
|
+
return false;
|
|
21754
|
+
if (na === nb)
|
|
21755
|
+
return true;
|
|
21756
|
+
return na.startsWith(`${nb}/`) || nb.startsWith(`${na}/`);
|
|
21757
|
+
}
|
|
21718
21758
|
async function mergeBranchToTarget(repoDir, sourceBranch, targetBranch) {
|
|
21719
21759
|
const mainRepo = await resolveMainRepoRoot(repoDir) ?? repoDir;
|
|
21720
21760
|
const ancestor = await runGitChecked(mainRepo, ["merge-base", "--is-ancestor", sourceBranch, targetBranch]);
|
|
@@ -21733,18 +21773,6 @@ async function mergeBranchToTarget(repoDir, sourceBranch, targetBranch) {
|
|
|
21733
21773
|
`- **原因**: 目标分支 \`${targetBranch}\` 正被工作树 \`${checkedOutPath}\` 检出;自动推进分支指针会使该工作树的暂存区/工作区与分支引用失步,本次未执行任何合并动作。`,
|
|
21734
21774
|
`- **处理**: 请在该工作树手动执行 \`git merge ${sourceBranch}\`,或移除该工作树(\`git worktree remove ${checkedOutPath}\`)后重试。`
|
|
21735
21775
|
].join(`
|
|
21736
|
-
`)
|
|
21737
|
-
};
|
|
21738
|
-
}
|
|
21739
|
-
const status = await runGit(mainRepo, ["status", "--porcelain"]);
|
|
21740
|
-
if (status.trim().length > 0) {
|
|
21741
|
-
return {
|
|
21742
|
-
success: false,
|
|
21743
|
-
conflict: false,
|
|
21744
|
-
blockedMessage: [
|
|
21745
|
-
`- **原因**: 目标分支 \`${targetBranch}\` 正被主仓库检出,且主仓库存在未提交改动;为避免覆盖本地改动,本次未执行任何合并动作(分支引用未动、无半成品)。`,
|
|
21746
|
-
`- **处理**: 请先 commit 或 stash 主仓库改动后重试;或自行执行 \`git merge ${sourceBranch}\`(真实合并自带脏工作区保护)。`
|
|
21747
|
-
].join(`
|
|
21748
21776
|
`)
|
|
21749
21777
|
};
|
|
21750
21778
|
}
|
|
@@ -21755,6 +21783,65 @@ async function mergeBranchToTarget(repoDir, sourceBranch, targetBranch) {
|
|
|
21755
21783
|
}
|
|
21756
21784
|
return { success: false, conflict: true };
|
|
21757
21785
|
}
|
|
21786
|
+
const mergedTreeOid = mergeTree.stdout.split(`
|
|
21787
|
+
`)[0].trim();
|
|
21788
|
+
const statusRes = await runGitChecked(mainRepo, ["status", "--porcelain"]);
|
|
21789
|
+
if (!statusRes.success) {
|
|
21790
|
+
throw new Error(`无法读取主仓库工作区状态:${statusRes.stderr}`);
|
|
21791
|
+
}
|
|
21792
|
+
const entries = parseStatusEntries(statusRes.stdout);
|
|
21793
|
+
if (entries.length > 0) {
|
|
21794
|
+
const dirtyPaths = [...new Set(entries.flatMap((e) => e.paths))];
|
|
21795
|
+
const written = parseDiffNameOnly(await runGit(mainRepo, ["diff", "--name-only", "--no-renames", targetBranch, mergedTreeOid]));
|
|
21796
|
+
const overlap = written.filter((w) => dirtyPaths.some((d) => pathsOverlap(d, w)));
|
|
21797
|
+
if (overlap.length > 0) {
|
|
21798
|
+
return {
|
|
21799
|
+
success: false,
|
|
21800
|
+
conflict: false,
|
|
21801
|
+
blockedMessage: [
|
|
21802
|
+
`- **原因**: 目标分支 \`${targetBranch}\` 正被主仓库检出,且以下本地改动文件与任务组合并将写入的文件重合;为避免覆盖本地改动,本次未执行任何合并动作(分支引用未动、无半成品):`,
|
|
21803
|
+
...overlap.map((f) => ` - \`${f}\``),
|
|
21804
|
+
`- **处理**: 请先 commit/stash/移除上述文件的本地改动后重试;或自行执行 \`git merge ${sourceBranch}\`(真实合并自带脏工作区保护)。`
|
|
21805
|
+
].join(`
|
|
21806
|
+
`)
|
|
21807
|
+
};
|
|
21808
|
+
}
|
|
21809
|
+
const stagedPaths = [...new Set(entries.filter((e) => e.x !== " " && e.x !== "?").flatMap((e) => e.paths))];
|
|
21810
|
+
if (stagedPaths.length > 0) {
|
|
21811
|
+
const unstagedDelta = await runGit(mainRepo, ["diff", "--name-only", "--", ...stagedPaths]);
|
|
21812
|
+
if (parseDiffNameOnly(unstagedDelta).length > 0) {
|
|
21813
|
+
return {
|
|
21814
|
+
success: false,
|
|
21815
|
+
conflict: false,
|
|
21816
|
+
blockedMessage: [
|
|
21817
|
+
`- **原因**: 主仓库存在部分暂存文件(同一文件既有已暂存又有未暂存改动),自动合并会改变暂存粒度;本次未执行任何合并动作(分支引用未动、无半成品)。`,
|
|
21818
|
+
`- **处理**: 请先 commit 或 stash,或补全/取消暂存后重试;或自行执行 \`git merge ${sourceBranch}\`。`
|
|
21819
|
+
].join(`
|
|
21820
|
+
`)
|
|
21821
|
+
};
|
|
21822
|
+
}
|
|
21823
|
+
let unstagedByUs = false;
|
|
21824
|
+
try {
|
|
21825
|
+
const restore = await runGitChecked(mainRepo, ["restore", "--staged", "--", ...stagedPaths]);
|
|
21826
|
+
if (!restore.success)
|
|
21827
|
+
throw new Error(`git restore --staged 失败(未执行合并):${restore.stderr}`);
|
|
21828
|
+
unstagedByUs = true;
|
|
21829
|
+
const mergeResult = await runGitChecked(mainRepo, ["merge", "--no-ff", sourceBranch]);
|
|
21830
|
+
if (!mergeResult.success) {
|
|
21831
|
+
await runGitChecked(mainRepo, ["merge", "--abort"]);
|
|
21832
|
+
return { success: false, conflict: true };
|
|
21833
|
+
}
|
|
21834
|
+
return { success: true, conflict: false };
|
|
21835
|
+
} finally {
|
|
21836
|
+
if (unstagedByUs) {
|
|
21837
|
+
const readd = await runGitChecked(mainRepo, ["add", "--", ...stagedPaths]);
|
|
21838
|
+
if (!readd.success) {
|
|
21839
|
+
throw new Error(`合并后还原暂存态失败(git add):${readd.stderr};请人工核对这些文件的暂存状态`);
|
|
21840
|
+
}
|
|
21841
|
+
}
|
|
21842
|
+
}
|
|
21843
|
+
}
|
|
21844
|
+
}
|
|
21758
21845
|
const mergeResult = await runGitChecked(mainRepo, ["merge", "--no-ff", sourceBranch]);
|
|
21759
21846
|
if (!mergeResult.success) {
|
|
21760
21847
|
await runGitChecked(mainRepo, ["merge", "--abort"]);
|
|
@@ -30075,7 +30162,7 @@ var TOOL_SPECS = {
|
|
|
30075
30162
|
execute: (args, ctx) => statusExecute({ change_id: args.change_id }, ctx)
|
|
30076
30163
|
},
|
|
30077
30164
|
opx_orch_complete_task_group: {
|
|
30078
|
-
description: "完成任务组收尾:合并 task-group 分支到 baseBranch → 清理 worktree 与分支。须在收尾验证(verify_cleanup
|
|
30165
|
+
description: "完成任务组收尾:合并 task-group 分支到 baseBranch → 清理 worktree 与分支。须在收尾验证(verify_cleanup)通过后调用。合并冲突、主仓库本地改动文件与合并写入文件重合、或存在部分暂存文件时中止并返回 blocked(保留 worktree/分支);主仓库无关脏文件不阻塞合并。",
|
|
30079
30166
|
schema: completeTaskGroupSchema,
|
|
30080
30167
|
execute: (args, ctx) => completeTaskGroupExecute(args, ctx)
|
|
30081
30168
|
},
|
|
@@ -30120,7 +30207,7 @@ async function ensureDefaultUnattended(args, ctx) {
|
|
|
30120
30207
|
}
|
|
30121
30208
|
} catch {}
|
|
30122
30209
|
}
|
|
30123
|
-
var PKG_VERSION = "0.
|
|
30210
|
+
var PKG_VERSION = "0.136.0";
|
|
30124
30211
|
function buildMcpServer(worktree, opts = {}) {
|
|
30125
30212
|
const mcp = new McpServer({ name: "openspec-agents", version: PKG_VERSION });
|
|
30126
30213
|
for (const [name, spec] of Object.entries(TOOL_SPECS)) {
|
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
- **多工具支持**:Claude Code、Codex、ZCode、DeepSeek Harness(DSH)、OpenCode 均可原生接入。
|
|
13
13
|
- **规范驱动**:直接消费 OpenSpec 变更规范(proposal / design / tasks / specs),流程与规范一一对应。
|
|
14
|
+
- **提案标准化**:内置 OpenSpec 提案标准(关联场景清点、契约变更详列、动笔前访谈与用户确认),一键初始化到任意项目,在提 change 阶段防漏考虑、防拆分割裂。
|
|
14
15
|
- **智能体团队**:不再是单个 AI 从头写到尾,而是「实现者 + 审查者 + 质量把关者」分工协作,结果更稳。
|
|
15
16
|
- **质量门禁**:实现之后经过工具检查、任务验证与多维度质量审查;发现的问题自动回退修复,也可按规则豁免并留痕。任务验证层(任务审查 / 合并审查)以通过(passed)提交时必须逐项确认全部已申报任务(verified_tasks 全覆盖为工具强制门禁);已通过但漏带确认导致阻塞时,审查者可补交确认结果解除阻塞。simple 模式下验证按性质分流——开发者执行确定性检查并申报,审查者分级复验(低成本项实跑、高成本项核验申报加抽样重放)并专注对抗性判断。
|
|
16
17
|
- **隔离执行**:每个变更在独立的环境中实施,互不干扰;变更完成后合并并清理。
|
|
@@ -72,6 +73,20 @@ npm install -D @ifrankwang/openspec-agents
|
|
|
72
73
|
|
|
73
74
|
如果已经安装,只需说一句「请运行 openspec-agents 编排流程」即可开工。AI 会负责完成安装、初始化、任务分派与质量把关的全部步骤。
|
|
74
75
|
|
|
76
|
+
## 标准化提 change(提案标准初始化)
|
|
77
|
+
|
|
78
|
+
插件内置一套 OpenSpec 提案标准,在「提 change」阶段就把好质量关:
|
|
79
|
+
|
|
80
|
+
- **关联场景清点**:动笔前以代码检索为证据,枚举变更触及能力的全部消费方(功能入口、共享处理逻辑、数据流下游);共享处理逻辑变更时全部消费入口必须同一 change 统一适配,防止同一逻辑两边割裂维护。
|
|
81
|
+
- **契约变更详列 / 动笔前访谈门 / 用户确认门**:DDL 与接口契约逐条列明;决策树访谈穷尽疑点;方案摘要经用户确认后才动笔。
|
|
82
|
+
- **任务组实质变更主体**:任务组以真实代码变更为主体,禁止收尾验证组与人工门禁任务。
|
|
83
|
+
|
|
84
|
+
在装了插件的工具里,于目标项目的**常规主代理会话**(非编排流程中的主代理——编排人格禁止修改文件,无法执行初始化)说一句:
|
|
85
|
+
|
|
86
|
+
> 初始化 openspec 提案标准
|
|
87
|
+
|
|
88
|
+
即可把标准写入该项目(`openspec/config.yaml` + `openspec/schemas/spec-driven-standard/`),此后该项目用 openspec 提 change 自动遵循标准。已初始化的项目重跑即升级到最新标准,项目自定义 rules 条目与 context 保留;标准方法论自包含,不依赖任何外部 skill。
|
|
89
|
+
|
|
75
90
|
## 工作原理(简述)
|
|
76
91
|
|
|
77
92
|
1. **输入**:项目中的 OpenSpec 变更规范(变更说明、设计、任务清单、验收规格)。
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: openspec-standard
|
|
3
|
+
description: OpenSpec 提案标准初始化与升级——把标准版提案规则(openspec/config.yaml + spec-driven-standard schema 全套)安装到当前项目,统一后续提 change 的质量门槛(关联场景清点、契约变更详列、动笔前访谈与用户确认)。当用户要求初始化、升级或标准化 openspec 提案标准/提案规范时使用。
|
|
4
|
+
capabilities: ["openspec-standard"]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 职责
|
|
8
|
+
|
|
9
|
+
把本 skill 目录 `templates/` 下的标准文件安装到当前项目的 `openspec/` 目录,使该项目此后用 openspec 提 change 时自动遵循统一标准:关联场景清点(共享处理逻辑的变更须与其全部消费入口同一 change 统一适配)、契约变更详列、动笔前访谈门与用户确认门、任务组实质变更主体等。幂等:重跑即把标准升级到插件当前版本,项目自定义内容按下方合并策略保留。
|
|
10
|
+
|
|
11
|
+
## 前置检查
|
|
12
|
+
|
|
13
|
+
- 当前项目根不存在 `openspec/` 目录 → 该项目尚未初始化 OpenSpec。提示用户先完成 openspec 初始化(建立 `openspec/` 目录与基础配置)后再执行本 skill,终止。
|
|
14
|
+
|
|
15
|
+
## 安装/升级流程
|
|
16
|
+
|
|
17
|
+
1. 读取本 skill 目录 `templates/` 下全部文件。
|
|
18
|
+
2. `openspec/config.yaml`:
|
|
19
|
+
- 项目尚无该文件 → 直接写入模板内容;
|
|
20
|
+
- 已存在 → 合并升级:`schema:` 一律改为 `spec-driven-standard`;`context:` 保留项目已有内容(无则用模板内容);`rules.*` / `operations.*` 各小节中,与标准条目同源的条目替换为模板最新文本,项目自定义条目(模板中不存在的)原样保留并追加在同小节标准条目之后。
|
|
21
|
+
3. `openspec/schemas/spec-driven-standard/`:整目录写入(schema.yaml、README.md、`templates/` 下五个文档模板);该目录为标准自有内容,已存在时直接覆盖。
|
|
22
|
+
4. 项目既有其它 schema 目录一律不动(既有 change 的 `.openspec.yaml` 可能引用);在汇报中说明:确认没有 change 再引用旧 schema 后,可手动删除旧目录。
|
|
23
|
+
5. 汇报:本次新建/更新/保留的文件与 config.yaml 条目清单;如项目此前使用其它 schema,说明此后新 change 将使用 spec-driven-standard。
|
|
24
|
+
|
|
25
|
+
## 约束
|
|
26
|
+
|
|
27
|
+
- 只允许写 `openspec/config.yaml` 与 `openspec/schemas/spec-driven-standard/` 下的文件,不修改项目其它任何文件。
|
|
28
|
+
- 标准文本原样落盘,不自行改写;项目特化约束(技术栈、分层架构、质量门工具等)属于项目 AGENTS.md,不在本 skill 范围。
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
schema: spec-driven-standard
|
|
2
|
+
|
|
3
|
+
context: |
|
|
4
|
+
项目技术背景、架构约定与工程规范以项目根目录 AGENTS.md 为准。
|
|
5
|
+
本文件仅承载 OpenSpec 流程的通用规则;与项目相关的特化约束
|
|
6
|
+
(技术栈、分层架构、数据库约定、质量门、方法论选择等)一律写入
|
|
7
|
+
项目 AGENTS.md,生成与复核子代理会被指示读取它。
|
|
8
|
+
explore 模式与 proposal 侧同源同严:凡涉及理清需求,必须按
|
|
9
|
+
spec-driven-standard schema clarify 指令的决策树访谈方法论逐轮穷尽
|
|
10
|
+
疑点;疑点未穷尽不得收束结论、不得捕获任何变更工件
|
|
11
|
+
(proposal/specs/design/tasks),不得以「需求简单」等理由跳过。
|
|
12
|
+
|
|
13
|
+
rules:
|
|
14
|
+
proposal:
|
|
15
|
+
- "关联场景清点(强制):proposal 动笔前必须完成「关联场景清点」并以代码检索为证据记入 clarify.md 的「关联场景清点(Related-Surface Survey)」小节:枚举本变更触及能力的全部消费方与关联面——功能入口(API 端点、导入/导出入口、定时任务、消息消费、前端调用等)、共享处理逻辑(被多个入口复用的处理链路)、数据模型与数据流下游(表/字段/事件/导出产物的读写双方),每条附代码位置证据(文件/类/方法)。凡共享处理逻辑发生变更,其全部消费入口必须在同一 change 内统一适配,禁止按入口分别提 change 导致共享逻辑两边割裂维护;确需拆分时必须在 proposal.md 单列「拆分边界」小节论证(每个 change 覆盖哪些入口、共享逻辑变更归属哪个 change、入口间契约),并经用户确认。proposal.md 必须单列「关联场景(Related Surface)」小节逐条列出清点结论与划界结论;泛写「无关联影响」而不附检索证据视为 proposal 未完成。"
|
|
16
|
+
- "契约变更详列(强制):凡变更涉及 DDL(建表/加列/索引/约束)或接口契约(新增/修改端点、请求响应结构、错误码),proposal.md 必须单列「契约变更点」小节逐条列出——DDL 逐表逐列给全表名、列名、类型、可空性、默认值与用途;API 逐端点给全方法+路径、请求/响应结构变化、新增/修改标记与兼容性影响。泛写「有契约变更」不逐条展开视为 proposal 未完成。"
|
|
17
|
+
- "动笔前访谈门(强制):proposal/specs/design/tasks 任一文档动笔前,必须已按 spec-driven-standard schema clarify 指令的决策树访谈方法论完成对用户的访谈——把方案建模为决策树逐轮穷尽疑点:每轮将前提已落定、当前可回答的问题一次性编号抛出并逐题附推荐答案,用户答复后重算下一轮前沿,直到决策树所有分支走完、无未决疑点、无静默假设;访谈问答全程记入 clarify.md(Open Questions / User Answers / 关联场景清点)。事实类问题必须自行查代码与 spec 求证,不抛给用户;疑点未穷尽时禁止写任何文档,不得以「需求简单」等理由跳过。"
|
|
18
|
+
- "动笔前用户确认门(强制):proposal/specs/design/tasks 任一文档动笔前,clarify.md 的 Architecture Direction 必须已记录用户对方案摘要的明确确认;确认必须以普通消息直接列出方案摘要要素点(要素清单见 spec-driven-standard schema clarify 指令的 Plan confirmation gate)并停下等待用户直接文字回复,禁止通过提问工具发起确认,提问工具的选项点击不视为确认;未记录时禁止写任何文档——主代理亲自写时先停下向用户补确认,生成子代理则立即终止并向编排方报告,不得以「需求简单」等理由跳过或代用户确认。"
|
|
19
|
+
specs:
|
|
20
|
+
- "数据行为需求(如\"系统 SHALL 持久化 X 含审计字段\")放 spec;物理 DDL 放 design.md。"
|
|
21
|
+
- "MODIFIED 的需求描述直接给出变更后的终态契约表述(\"系统 SHALL 使用 Y\"),供归档合并时原样落入主 spec;禁止以\"原 X 改为 Y\"的历史对比充当主 spec 内容。"
|
|
22
|
+
- "同一语义只保留一条需求:修改已有需求用 MODIFIED,不得以新标题重复描述同一能力。"
|
|
23
|
+
tasks:
|
|
24
|
+
- "每个任务组必须以实质变更为主体(强制):组内至少一个任务承载真实的代码/配置行为变更(实现 + 测试);文档/脚本/部署文档同步、验证、回归、质量门只是随行工作,不构成组的实质内容。任务条数不等于实质——排到末位的剩余工作若只剩同步与验证,必须并入前一个功能组,禁止为其新开组(反例:「阶段二收尾:文档、脚本与全量验证」式收尾组)。"
|
|
25
|
+
- "任务组覆盖关联入口(强制):tasks.md 的任务组划分必须覆盖 proposal.md「关联场景」小节划入本 change 的全部消费入口;任一划入入口缺少对应适配任务视为 tasks 未完成。"
|
|
26
|
+
- "真跨组依赖应拆为独立前置 change,在组标题下用 `> **前置 change:**` 标注,组间不可并行。"
|
|
27
|
+
- "禁止人工门禁任务:不得生成\"等待用户确认/人工验收/人工回归后再继续\"式的任务或检查点;验证一律以自动化手段(编译、测试、质量门)闭环,确需人工执行的回归留档于 design.md 的 Risks/Migration,不进入 tasks.md。"
|
|
28
|
+
- "禁止真实调用生产接口的任务:联调与验证只允许本地/测试环境(容器化测试、桩/mock 服务、本地 profile 的 API 测试),生产环境接口调用不得作为任务出现。"
|
|
29
|
+
|
|
30
|
+
operations:
|
|
31
|
+
archive:
|
|
32
|
+
guidance:
|
|
33
|
+
- "归档前必须完成变更同步:delta 与主 spec 有差异时禁止直接归档。"
|
|
34
|
+
- "归档同步无条件执行,无需用户确认:delta 与主 spec 存在差异时直接同步后归档,禁止提供\"不同步归档\"选项或就此询问用户;仅当同步失败或一致性校验无法收敛时停止并向用户报告。"
|
|
35
|
+
- "主 spec 只描述当前契约:应用 MODIFIED 时把\"原 X 改为 Y\"式描述改写为终态表述,已废弃组件(表/类/接口/端点)不得保留在主 spec。"
|
|
36
|
+
- "delta 出现移除/废弃/不再语义时,必须在主 spec 删除对应需求或场景,并在 delta spec 补写 `## REMOVED Requirements` 保持可追溯。"
|
|
37
|
+
- "同一语义只保留一条需求:与已有需求重叠时合并,不得新增重复标题。"
|
|
38
|
+
- "同步收敛:同步完成后主 spec 须完整反映 delta——ADDED 已存在、MODIFIED 为终态、REMOVED 已消失。"
|
|
39
|
+
- "归档前运行 `openspec validate` 校验 MODIFIED 与主 spec 一致性,并核对主 spec 无变更式表述残留、无 REMOVED 残留、无重复需求、无空 spec 目录、无残留 TBD;未通过先修复。"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# OpenSpec Schema: spec-driven-standard
|
|
2
|
+
|
|
3
|
+
通用 spec-driven 工作流模板:clarify → proposal → specs → design → tasks,
|
|
4
|
+
增强三点:
|
|
5
|
+
1. **clarify 前置门槛 + 关联场景清点**:每次变更先做需求澄清与关联场景清点
|
|
6
|
+
(枚举变更触及能力的全部消费方——功能入口 / 共享处理逻辑 / 数据流下游,
|
|
7
|
+
附代码证据;共享处理逻辑变更须与其全部消费入口同一 change 统一适配);
|
|
8
|
+
访谈结束后、写任何文档之前,主代理必须先向用户简单直接汇报方案(一句话
|
|
9
|
+
讲清做什么 + 是否动契约 + 取舍 + 影响面)并取得用户明确确认(HARD-GATE:
|
|
10
|
+
汇报须以普通消息直接列出要素点,禁止用提问工具发起确认,访谈中的问答
|
|
11
|
+
不视为确认),确认记录落在 clarify.md,后续文档不得偏离。
|
|
12
|
+
2. **主代理访谈直录 + 分级撰写 + 独立复核循环**:主代理亲自访谈,问答
|
|
13
|
+
直接记入 clarify.md(访谈阶段唯一落笔的文档,不设独立访谈文件);
|
|
14
|
+
架构方向经用户确认后,按复杂度分级产出其余文档(proposal → specs →
|
|
15
|
+
design → tasks,不逐文档分派)——简单变更由主代理本人按依赖顺序撰写
|
|
16
|
+
(省 token),复杂变更派发唯一一个独立生成子代理在同一会话内撰写(保
|
|
17
|
+
主代理上下文精简),分级判定与理由记入 clarify.md;无论哪级,主代理
|
|
18
|
+
必须派发独立复核子代理全量检查(结构校验 + 规则核对 + 与主 spec 交叉
|
|
19
|
+
核对),发现问题由主代理本人修复后再次复核,循环最多 3 轮,复核干净
|
|
20
|
+
通过(PASS)才算完成。完整三阶段编排契约写在 clarify 的 instruction
|
|
21
|
+
里——这是主代理必读的一份,复核派发由此可达;复杂变更的生成子代理
|
|
22
|
+
最终汇报必须提示主代理进入复核阶段,双保险防契约断链。
|
|
23
|
+
3. **自包含方法论**:决策树逐轮访谈(frontier 机制)全文内联在 clarify
|
|
24
|
+
instruction 的 Methodology 节,不依赖任何外部 skill。
|
|
25
|
+
|
|
26
|
+
## 目录
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
openspec/schemas/<schema-name>/
|
|
30
|
+
├── schema.yaml # 流程与文档结构定义(纯通用,无项目内容)
|
|
31
|
+
├── templates/ # 各文档模板(纯通用)
|
|
32
|
+
└── README.md # 本说明
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
配套文件(一并安装):项目根 `openspec/config.yaml`(通用流程规则)。
|
|
36
|
+
|
|
37
|
+
## 接入新项目
|
|
38
|
+
|
|
39
|
+
**方式一(推荐,插件一键初始化)**:安装了 openspec-agents 插件的 AI 编码
|
|
40
|
+
工具中,在目标项目的常规主代理会话里说一句「初始化 openspec 提案标准」,
|
|
41
|
+
即自动写入本 schema 目录与配套 `openspec/config.yaml`;已初始化的项目重跑
|
|
42
|
+
即升级到最新标准(项目自定义 rules 条目与 context 保留)。
|
|
43
|
+
|
|
44
|
+
**方式二(手动回退,无插件环境)**:
|
|
45
|
+
1. 把本目录整体 copy 到新项目 `openspec/schemas/spec-driven-standard/`。
|
|
46
|
+
2. 把通用版 `openspec/config.yaml` copy 到新项目,确认 `schema:` 指向
|
|
47
|
+
`spec-driven-standard`。
|
|
48
|
+
3. 在新项目 `AGENTS.md` 中写清项目特化约束(见下节),并确认每次变更的
|
|
49
|
+
`.openspec.yaml` 的 `schema:` 字段指向正确。
|
|
50
|
+
|
|
51
|
+
> 生成子代理(复杂变更时)与复核子代理会被指示读取项目 `AGENTS.md` 与
|
|
52
|
+
> `openspec/config.yaml`,因此项目特化约束写在 `AGENTS.md` 即可自动生效,
|
|
53
|
+
> schema 本身无需改动。
|
|
54
|
+
|
|
55
|
+
## 项目特化约束清单(写进 AGENTS.md 的内容)
|
|
56
|
+
|
|
57
|
+
| 类别 | 示例 |
|
|
58
|
+
|---|---|
|
|
59
|
+
| 技术栈 | 语言/框架/版本、构建工具、依赖管理 |
|
|
60
|
+
| 分层架构 | 分层/包组织、依赖方向、层间规则 |
|
|
61
|
+
| 领域约定 | 确定性 vs 不确定性分工、领域模型规范 |
|
|
62
|
+
| 数据库约定 | 表前缀、主键策略、审计字段、逻辑删除、迁移方式 |
|
|
63
|
+
| 质量门 | 格式/静态检查工具、测试策略、覆盖率门槛 |
|
|
64
|
+
| 方法论 | 澄清/评审/开发流程约定(如 TDD) |
|
|
65
|
+
|
|
66
|
+
## 三层分工(不重复原则)
|
|
67
|
+
|
|
68
|
+
| 位置 | 承载内容 | 例子 |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `schema.yaml` instruction | 生成方法论与文档格式要求(官方方法论为底) | 场景标题 4 个 #、SHALL/MUST、ADDED/MODIFIED/REMOVED、任务组格式、每组自带测试与质量门、关联场景清点维度 |
|
|
71
|
+
| `openspec/config.yaml` rules | **仅** schema 未覆盖的项目补充规则 | MODIFIED 终态契约表述、同语义一条需求、前置 change 标注格式、契约变更详列、访谈门与用户确认门 |
|
|
72
|
+
| 项目 `AGENTS.md` | 项目特化约束 | 技术栈、分层架构、数据库约定、质量门工具、方法论 |
|
|
73
|
+
|
|
74
|
+
同一规则只写一处:config.yaml rules 不重复 schema instruction 已有内容;
|
|
75
|
+
schema 不写项目专属内容。新增规则时先查上一层的 instruction 是否已覆盖。
|
|
76
|
+
|
|
77
|
+
官方升级 `openspec update` 会覆盖 `.agents/skills/` 下的 skill,本 schema
|
|
78
|
+
与 config.yaml 不受影响;CLI 升级后可重新对齐官方 `spec-driven`
|
|
79
|
+
(`openspec schema fork`)再叠加本模板的增强段落。
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
name: spec-driven-standard
|
|
2
|
+
version: 1
|
|
3
|
+
description: Spec-driven workflow with clarify gate, related-surface survey, tiered authoring (orchestrator or single subagent) and review loop - clarify → proposal → specs → design → tasks
|
|
4
|
+
artifacts:
|
|
5
|
+
- id: clarify
|
|
6
|
+
generates: clarify.md
|
|
7
|
+
description: Requirement and architecture clarification record
|
|
8
|
+
template: clarify.md
|
|
9
|
+
instruction: |
|
|
10
|
+
**Orchestration contract (addressed to YOU, the orchestrating agent)**:
|
|
11
|
+
Duties are split into three phases. You personally run Phase 1 and
|
|
12
|
+
Phase 3. Phase 2 is TIERED by change complexity: for SIMPLE changes
|
|
13
|
+
you write proposal/specs/design/tasks yourself; for COMPLEX changes a
|
|
14
|
+
single fresh generation subagent writes them. No subagent ever writes
|
|
15
|
+
clarify.md - you write it yourself as the interview record.
|
|
16
|
+
|
|
17
|
+
Phase 1 - Interview (you): investigate the codebase and existing
|
|
18
|
+
openspec/specs/ first, then interview the user in rounds over a
|
|
19
|
+
decision tree using the frontier mechanics (methodology below),
|
|
20
|
+
recording questions and answers directly into <changeRoot>/clarify.md
|
|
21
|
+
as you go (sections below) - there is no separate interview file.
|
|
22
|
+
HARD-GATE: the interview is over only when every branch of the decision
|
|
23
|
+
tree has been visited, no doubt is left open, nothing is silently
|
|
24
|
+
assumed; then first present a simple, direct plan summary to the user
|
|
25
|
+
and get an explicit confirmation of the direction - do NOT start
|
|
26
|
+
Phase 2 (authoring) until the user has confirmed it (details in
|
|
27
|
+
methodology below).
|
|
28
|
+
|
|
29
|
+
Phase 2 - Authoring (TIERED): only after the user has explicitly
|
|
30
|
+
confirmed the plan summary (HARD-GATE above), decide the tier using
|
|
31
|
+
the complexity criteria below, record the decision and one-line
|
|
32
|
+
rationale in clarify.md, then
|
|
33
|
+
follow the matching mode:
|
|
34
|
+
- SIMPLE tier: you personally produce the remaining artifacts
|
|
35
|
+
(proposal → specs → design → tasks) in dependency order. For each
|
|
36
|
+
artifact run `openspec instructions <artifact-id> --change
|
|
37
|
+
"<name>" --json` yourself, read the project's AGENTS.md and
|
|
38
|
+
openspec/config.yaml rules plus the dependency artifacts (starting
|
|
39
|
+
from the clarify.md you wrote), and write to the resolved output
|
|
40
|
+
path. Do not dispatch any subagent for authoring.
|
|
41
|
+
- COMPLEX tier: dispatch a single fresh general-purpose subagent
|
|
42
|
+
with the change name. It produces the remaining artifacts in one
|
|
43
|
+
session: for each it runs
|
|
44
|
+
`openspec instructions <artifact-id> --change "<name>" --json`
|
|
45
|
+
itself, reads the project's AGENTS.md and openspec/config.yaml
|
|
46
|
+
rules plus the dependency artifacts, and writes to the resolved
|
|
47
|
+
output path. It may only read/write inside the change directory
|
|
48
|
+
(openspec/changes/<name>/). It must not dispatch further
|
|
49
|
+
subagents, and its final report MUST end by telling you to start
|
|
50
|
+
Phase 3.
|
|
51
|
+
|
|
52
|
+
Complexity criteria - COMPLEX if ANY applies, otherwise SIMPLE:
|
|
53
|
+
- touches 2+ capabilities or 2+ task groups with cross-group
|
|
54
|
+
dependencies;
|
|
55
|
+
- significant data-model change (new tables, many columns, or any
|
|
56
|
+
data migration);
|
|
57
|
+
- new external dependency, new architectural pattern, or notable
|
|
58
|
+
security/performance/migration complexity;
|
|
59
|
+
- expected total artifact size would push your context near the
|
|
60
|
+
window limit if you wrote them yourself (large DDL, long task
|
|
61
|
+
lists, multi-spec changes).
|
|
62
|
+
When in doubt, choose COMPLEX: it keeps the orchestrating agent's
|
|
63
|
+
context lean and is the safer default.
|
|
64
|
+
|
|
65
|
+
Phase 3 - Review loop (you, once authoring is complete):
|
|
66
|
+
1. Dispatch an independent review subagent (fresh general-purpose
|
|
67
|
+
session — never reuse the generation subagent). It must:
|
|
68
|
+
- run `openspec validate "<name>" --strict` for structural checks
|
|
69
|
+
(note: validate takes the change name as a positional argument,
|
|
70
|
+
no --change flag);
|
|
71
|
+
- check every artifact against the openspec/config.yaml rules for
|
|
72
|
+
its stage, the schema templates/instructions, and cross-document
|
|
73
|
+
consistency (clarify direction ↔ proposal capabilities ↔ specs
|
|
74
|
+
requirements ↔ design decisions ↔ tasks with [spec:...]
|
|
75
|
+
traceability tags);
|
|
76
|
+
- read openspec/specs/ main specs (read-only) and cross-check:
|
|
77
|
+
proposal capabilities actually exist there, and delta
|
|
78
|
+
requirements/contracts (e.g. error codes, rate-limit semantics,
|
|
79
|
+
existing implementations) do not conflict with what the main
|
|
80
|
+
specs already establish;
|
|
81
|
+
- write findings to <changeRoot>/.review/issues.md, one block per
|
|
82
|
+
issue with fields: severity | file | problem | suggested fix;
|
|
83
|
+
write "PASS" when clean. The .review/ directory exists only for
|
|
84
|
+
this working file during the loop - never use it to record
|
|
85
|
+
anything else.
|
|
86
|
+
2. If issues.md is not "PASS", YOU fix every issue yourself
|
|
87
|
+
(change-directory documents only; you must not modify issues.md
|
|
88
|
+
itself, and you must not delegate fixes to a subagent — fixing
|
|
89
|
+
in your own context is the token-saving design).
|
|
90
|
+
3. Re-dispatch the review subagent for a fresh full review; loop
|
|
91
|
+
until "PASS" or 3 review rounds. After 3 rounds with remaining
|
|
92
|
+
issues, stop and present the leftover issues to the user.
|
|
93
|
+
4. On "PASS", delete the .review/ directory.
|
|
94
|
+
|
|
95
|
+
Do NOT hand over to the apply workflow before Phase 3 ends with
|
|
96
|
+
"PASS" (or with leftover issues explicitly accepted by the user).
|
|
97
|
+
|
|
98
|
+
Create the clarify document that records the requirement clarification and
|
|
99
|
+
architecture direction exploration BEFORE any proposal is written. This
|
|
100
|
+
artifact is MANDATORY for every change - it captures the think-before-build
|
|
101
|
+
phase, grounded in the actual codebase and existing specs.
|
|
102
|
+
|
|
103
|
+
Methodology:
|
|
104
|
+
- Investigate the codebase and existing openspec/specs/ first. Ground every
|
|
105
|
+
question in real code; do not theorize without reading the relevant modules.
|
|
106
|
+
- Surface unknowns across these dimensions and resolve them with the
|
|
107
|
+
user by interviewing in rounds over a decision tree (every decision
|
|
108
|
+
branches into the decisions that hang off it):
|
|
109
|
+
* Problem & intent: what problem is being solved? what is the success criteria?
|
|
110
|
+
* Scope: what is in/out of bounds? is the change decomposable into sub-changes?
|
|
111
|
+
* Related-surface survey (关联场景清点): enumerate every consumer of the
|
|
112
|
+
capability being changed, grounded in real code — functional entry points
|
|
113
|
+
(API endpoints, import/export entries, scheduled jobs, message consumers,
|
|
114
|
+
frontend calls), shared processing logic reused by multiple entry points,
|
|
115
|
+
and downstream data consumers (readers/writers of the tables, fields,
|
|
116
|
+
events, or export artifacts involved). Record each finding with code
|
|
117
|
+
evidence (file/class/method) in clarify.md's Related-Surface Survey
|
|
118
|
+
section. When shared processing logic changes, every consuming entry
|
|
119
|
+
point must be adapted within the SAME change; splitting into separate
|
|
120
|
+
changes requires an explicit boundary rationale confirmed by the user.
|
|
121
|
+
* Data model: which entities/tables/fields are involved? any new tables or columns?
|
|
122
|
+
* Business rules: which are deterministic vs non-deterministic, per the
|
|
123
|
+
project's conventions (see AGENTS.md)?
|
|
124
|
+
* Integration boundaries: external systems, APIs, ports/adapters?
|
|
125
|
+
* Architecture direction: propose 2-3 approaches with tradeoffs and a recommendation.
|
|
126
|
+
- Round mechanics: each round asks every question whose prerequisites
|
|
127
|
+
are already settled (the whole frontier at once) via the question
|
|
128
|
+
tool, numbered, each with your recommended answer; then wait for
|
|
129
|
+
the user's answers before the next round. A question whose answer
|
|
130
|
+
depends on one still open in this round belongs to a later round.
|
|
131
|
+
Facts are yours to find (codebase, specs, tools) - never ask the
|
|
132
|
+
user for anything you can look up yourself; the decisions are the
|
|
133
|
+
user's. Rounds continue until the frontier is empty: every branch
|
|
134
|
+
visited, no doubt left unresolved, nothing silently assumed.
|
|
135
|
+
- Challenge assumptions (yours and the user's). Reframe the problem when needed.
|
|
136
|
+
- Plan confirmation gate (HARD-GATE): once the interview rounds have
|
|
137
|
+
exhausted every open doubt (frontier empty) and before starting
|
|
138
|
+
Phase 2 (authoring — whether by you or
|
|
139
|
+
by a generation subagent), present a simple, direct plan summary to
|
|
140
|
+
the user and stop for confirmation. The summary must state: what the
|
|
141
|
+
change does in one sentence; whether it touches contracts (API
|
|
142
|
+
parameters or DDL); the chosen approach and its key trade-offs
|
|
143
|
+
versus the alternatives; the capabilities affected. Present it as a
|
|
144
|
+
plain message — do NOT use the question tool for this confirmation
|
|
145
|
+
(the question tool belongs to the interview rounds, not to this
|
|
146
|
+
confirmation) — and stop for the user's free-text reply. Interview
|
|
147
|
+
answers do NOT count as confirmation — the confirmation must be a
|
|
148
|
+
direct user response to this summary. If the user requests changes,
|
|
149
|
+
revise the summary and re-present; loop until the user explicitly
|
|
150
|
+
confirms. Record the confirmed summary in clarify.md's Architecture
|
|
151
|
+
Direction section. "Too simple to need clarification" is not a
|
|
152
|
+
valid reason to skip; the clarify record may be short but must
|
|
153
|
+
exist, and this confirmation step is still mandatory.
|
|
154
|
+
|
|
155
|
+
Sections (see template):
|
|
156
|
+
- **Open Questions**: the unknowns surfaced, one per bullet.
|
|
157
|
+
- **User Answers**: the user's responses, captured verbatim or summarized.
|
|
158
|
+
- **Related-Surface Survey**: the related-surface survey output (关联场景清点) —
|
|
159
|
+
every consuming entry point / shared processing logic / downstream
|
|
160
|
+
consumer with code evidence, and the scoping verdict for each. Shared
|
|
161
|
+
processing logic changes must be covered together with all their
|
|
162
|
+
consuming entry points in the same change.
|
|
163
|
+
- **Architecture Direction**: the crystallized approach, key decisions,
|
|
164
|
+
alternatives considered, and the confirmed plan summary with
|
|
165
|
+
the user's explicit confirmation. This is the HARD-GATE output.
|
|
166
|
+
- **Initial Capabilities**: preliminary guess of which specs will be touched
|
|
167
|
+
(kebab-case). Finalized in proposal.md.
|
|
168
|
+
|
|
169
|
+
This is the foundation for proposal, specs, design, and tasks. All later
|
|
170
|
+
artifacts must stay consistent with the direction recorded here.
|
|
171
|
+
requires: []
|
|
172
|
+
- id: proposal
|
|
173
|
+
generates: proposal.md
|
|
174
|
+
description: Initial proposal document outlining the change
|
|
175
|
+
template: proposal.md
|
|
176
|
+
instruction: |
|
|
177
|
+
**Generation (process constraint)**: This artifact is written by exactly
|
|
178
|
+
one author — either YOU, the orchestrating agent (SIMPLE tier), or the
|
|
179
|
+
ONE independent generation subagent dispatched once for the change
|
|
180
|
+
(COMPLEX tier) — never one subagent per artifact, and never by both.
|
|
181
|
+
The full orchestration contract (interview, tier decision, authoring,
|
|
182
|
+
review loop) lives in the clarify artifact's instruction; read it
|
|
183
|
+
first if you have not. The author runs
|
|
184
|
+
`openspec instructions <artifact-id> --change "<name>" --json`, reads
|
|
185
|
+
the project's AGENTS.md and openspec/config.yaml rules plus the
|
|
186
|
+
dependency artifacts (including the clarify.md written by the
|
|
187
|
+
orchestrating agent), then writes to the resolved output path. If a
|
|
188
|
+
generation subagent is the author, it may only read/write inside the
|
|
189
|
+
change directory (openspec/changes/<name>/).
|
|
190
|
+
|
|
191
|
+
Create the proposal document that establishes WHY this change is needed.
|
|
192
|
+
|
|
193
|
+
Sections:
|
|
194
|
+
- **Why**: 1-2 sentences on the problem or opportunity. What problem does this solve? Why now?
|
|
195
|
+
- **What Changes**: Bullet list of changes. Be specific about new capabilities, modifications, or removals. Mark breaking changes with **BREAKING**.
|
|
196
|
+
- **Related Surface (关联场景)**: the survey conclusions from clarify.md —
|
|
197
|
+
every consuming entry point / shared processing logic / downstream
|
|
198
|
+
consumer with code evidence and its scoping verdict (adapted in this
|
|
199
|
+
change, or explicitly justified out). When shared processing logic
|
|
200
|
+
changes, every consuming entry point must be adapted in THIS change;
|
|
201
|
+
splitting into separate changes requires a boundary rationale section
|
|
202
|
+
confirmed by the user. A bare "no related impact" without search
|
|
203
|
+
evidence counts as an incomplete proposal.
|
|
204
|
+
- **Capabilities**: Identify which specs will be created or modified:
|
|
205
|
+
- **New Capabilities**: List capabilities being introduced. Each becomes a new `specs/<capability-path>/spec.md`. Use kebab-case for path segments you introduce (e.g., `user-auth` or `identity/user-auth`) and follow the project's existing spec organization.
|
|
206
|
+
- **Modified Capabilities**: List existing capabilities whose REQUIREMENTS are changing. Only include if spec-level behavior changes (not just implementation details). Each needs a delta spec file. Use the exact existing path under `openspec/specs/`. Leave empty if no requirement changes.
|
|
207
|
+
- **Impact**: Affected code, APIs, dependencies, or systems.
|
|
208
|
+
|
|
209
|
+
IMPORTANT: The Capabilities section is critical. It creates the contract between
|
|
210
|
+
proposal and specs phases. Research existing specs before filling this in.
|
|
211
|
+
Each capability listed here will need a corresponding spec file.
|
|
212
|
+
|
|
213
|
+
Every change must either declare at least one capability (new or
|
|
214
|
+
modified) or explicitly opt out of specs: `openspec validate` rejects a
|
|
215
|
+
change with zero deltas unless the change's `.openspec.yaml` sets
|
|
216
|
+
`skip_specs: true`. Use `skip_specs: true` only when no spec-level
|
|
217
|
+
behavior changes (pure refactor, tooling, docs) - specs describe
|
|
218
|
+
behavior, so if behavior does not change, no spec should change either.
|
|
219
|
+
Do not invent a requirement just to satisfy validation.
|
|
220
|
+
|
|
221
|
+
Keep it concise (1-2 pages). Focus on the "why" not the "how" -
|
|
222
|
+
implementation details belong in design.md. Stay consistent with
|
|
223
|
+
clarify.md's architecture direction; if the proposal drifts from clarify,
|
|
224
|
+
update clarify first and get user confirmation.
|
|
225
|
+
|
|
226
|
+
This is the foundation - specs, design, and tasks all build on this.
|
|
227
|
+
requires:
|
|
228
|
+
- clarify
|
|
229
|
+
- id: specs
|
|
230
|
+
generates: "specs/**/*.md"
|
|
231
|
+
description: Detailed specifications for the change
|
|
232
|
+
template: spec.md
|
|
233
|
+
instruction: |
|
|
234
|
+
**Generation (process constraint)**: This artifact is written by exactly
|
|
235
|
+
one author — either YOU, the orchestrating agent (SIMPLE tier), or the
|
|
236
|
+
ONE independent generation subagent dispatched once for the change
|
|
237
|
+
(COMPLEX tier) — never one subagent per artifact, and never by both.
|
|
238
|
+
The full orchestration contract (interview, tier decision, authoring,
|
|
239
|
+
review loop) lives in the clarify artifact's instruction; read it
|
|
240
|
+
first if you have not. The author runs
|
|
241
|
+
`openspec instructions <artifact-id> --change "<name>" --json`, reads
|
|
242
|
+
the project's AGENTS.md and openspec/config.yaml rules plus the
|
|
243
|
+
dependency artifacts (including the clarify.md written by the
|
|
244
|
+
orchestrating agent), then writes to the resolved output path. If a
|
|
245
|
+
generation subagent is the author, it may only read/write inside the
|
|
246
|
+
change directory (openspec/changes/<name>/).
|
|
247
|
+
|
|
248
|
+
Create specification files that define WHAT the system should do.
|
|
249
|
+
|
|
250
|
+
A spec is a behavior contract, not an implementation plan.
|
|
251
|
+
|
|
252
|
+
Good spec content:
|
|
253
|
+
- Observable behavior users or downstream systems rely on
|
|
254
|
+
- Inputs, outputs, and error conditions
|
|
255
|
+
- External constraints (security, privacy, reliability, compatibility)
|
|
256
|
+
- Scenarios that can be tested or explicitly validated
|
|
257
|
+
|
|
258
|
+
Avoid in specs:
|
|
259
|
+
- Internal class/function names
|
|
260
|
+
- Library or framework choices
|
|
261
|
+
- Step-by-step implementation details
|
|
262
|
+
- Detailed execution plans (those belong in design.md or tasks.md)
|
|
263
|
+
|
|
264
|
+
Quick test: if the implementation can change without changing externally
|
|
265
|
+
visible behavior, it likely does not belong in the spec.
|
|
266
|
+
|
|
267
|
+
Create one spec file per capability listed in the proposal's Capabilities section.
|
|
268
|
+
`<capability-path>` is the spec directory relative to `specs/` (for example,
|
|
269
|
+
`user-auth` or `identity/user-auth`). Preserve the full path:
|
|
270
|
+
- New capabilities: use the exact path from the proposal at `specs/<capability-path>/spec.md`. Any path segment newly introduced in the proposal must be kebab-case. Follow the project's existing organization; do not add a new domain level when the project uses a flat layout.
|
|
271
|
+
- Modified capabilities: use the exact existing path from `openspec/specs/<capability-path>/` when creating the delta at `specs/<capability-path>/spec.md`. Do not move or rename the capability.
|
|
272
|
+
|
|
273
|
+
There must be at least one spec file unless the change's `.openspec.yaml`
|
|
274
|
+
sets `skip_specs: true` (no spec-level behavior change) - `openspec validate`
|
|
275
|
+
rejects a zero-delta change without that marker. If the proposal lists no
|
|
276
|
+
capabilities and `skip_specs` is not set, revisit the proposal first.
|
|
277
|
+
|
|
278
|
+
Delta operations (use ## headers):
|
|
279
|
+
- **ADDED Requirements**: New capabilities
|
|
280
|
+
- **MODIFIED Requirements**: Changed behavior - MUST include full updated content
|
|
281
|
+
- **REMOVED Requirements**: Deprecated features - MUST include **Reason** and **Migration**
|
|
282
|
+
- **RENAMED Requirements**: Name changes only - use FROM:/TO: format
|
|
283
|
+
|
|
284
|
+
Format requirements:
|
|
285
|
+
- Each requirement: `### Requirement: <name>` followed by description
|
|
286
|
+
- Use SHALL/MUST for normative requirements (avoid should/may)
|
|
287
|
+
- Each scenario: `#### Scenario: <name>` with WHEN/THEN format
|
|
288
|
+
- **CRITICAL**: Scenarios MUST use exactly 4 hashtags (`####`). Using 3 hashtags or bullets will fail silently.
|
|
289
|
+
- Every requirement MUST have at least one scenario.
|
|
290
|
+
|
|
291
|
+
New capabilities only: start the delta spec with a `## Purpose` section -
|
|
292
|
+
one or two sentences (50+ characters, or `openspec validate --strict`
|
|
293
|
+
reports it as too brief) describing what the capability is for. Archive
|
|
294
|
+
copies it into the main spec it creates; without it the new main spec is
|
|
295
|
+
left with a `TBD ... Update Purpose after archive` placeholder to fill in
|
|
296
|
+
by hand. Do NOT add `## Purpose` to a delta for an existing capability -
|
|
297
|
+
that spec already has one and the delta's is ignored. To change an
|
|
298
|
+
existing capability's Purpose - including a leftover `TBD` placeholder -
|
|
299
|
+
edit `openspec/specs/<capability-path>/spec.md` directly.
|
|
300
|
+
|
|
301
|
+
MODIFIED requirements workflow:
|
|
302
|
+
1. Locate the existing requirement in openspec/specs/<capability-path>/spec.md
|
|
303
|
+
2. Copy the ENTIRE requirement block (from `### Requirement:` through all scenarios)
|
|
304
|
+
3. Paste under `## MODIFIED Requirements` and edit to reflect new behavior
|
|
305
|
+
4. Ensure header text matches exactly (whitespace-insensitive)
|
|
306
|
+
|
|
307
|
+
Common pitfall: Using MODIFIED with partial content loses detail at archive time.
|
|
308
|
+
If adding new concerns without changing existing behavior, use ADDED instead.
|
|
309
|
+
|
|
310
|
+
Specs should be testable - each scenario is a potential test case.
|
|
311
|
+
requires:
|
|
312
|
+
- proposal
|
|
313
|
+
- id: design
|
|
314
|
+
generates: design.md
|
|
315
|
+
description: Technical design document with implementation details
|
|
316
|
+
template: design.md
|
|
317
|
+
instruction: |
|
|
318
|
+
**Generation (process constraint)**: This artifact is written by exactly
|
|
319
|
+
one author — either YOU, the orchestrating agent (SIMPLE tier), or the
|
|
320
|
+
ONE independent generation subagent dispatched once for the change
|
|
321
|
+
(COMPLEX tier) — never one subagent per artifact, and never by both.
|
|
322
|
+
The full orchestration contract (interview, tier decision, authoring,
|
|
323
|
+
review loop) lives in the clarify artifact's instruction; read it
|
|
324
|
+
first if you have not. The author runs
|
|
325
|
+
`openspec instructions <artifact-id> --change "<name>" --json`, reads
|
|
326
|
+
the project's AGENTS.md and openspec/config.yaml rules plus the
|
|
327
|
+
dependency artifacts (including the clarify.md written by the
|
|
328
|
+
orchestrating agent), then writes to the resolved output path. If a
|
|
329
|
+
generation subagent is the author, it may only read/write inside the
|
|
330
|
+
change directory (openspec/changes/<name>/).
|
|
331
|
+
|
|
332
|
+
Create the design document that explains HOW to implement the change.
|
|
333
|
+
|
|
334
|
+
When to include design.md (create only if any apply):
|
|
335
|
+
- Cross-cutting change (multiple services/modules) or new architectural pattern
|
|
336
|
+
- New external dependency or significant data model changes
|
|
337
|
+
- Security, performance, or migration complexity
|
|
338
|
+
- Ambiguity that benefits from technical decisions before coding
|
|
339
|
+
|
|
340
|
+
Sections:
|
|
341
|
+
- **Context**: Only the current state and constraints needed to explain the approach. Reference the proposal for motivation instead of restating it (e.g., "See proposal.md - Why").
|
|
342
|
+
- **Goals / Non-Goals**: What this design achieves and explicitly excludes. Don't restate the proposal's scope - add only design-level boundaries.
|
|
343
|
+
- **Decisions**: Key technical choices with rationale (why X over Y?). Include alternatives considered for each decision.
|
|
344
|
+
- **Data Model**: MANDATORY when the change involves persistence. Complete
|
|
345
|
+
DDL (CREATE TABLE with columns, types, constraints), field descriptions,
|
|
346
|
+
indexes, and audit fields. Follow the project's persistence conventions
|
|
347
|
+
(see AGENTS.md / openspec/config.yaml rules). If the change does not
|
|
348
|
+
involve persistence, state that explicitly ("本变更不涉及持久化" or the
|
|
349
|
+
project's equivalent phrasing).
|
|
350
|
+
- **Risks / Trade-offs**: Known limitations, things that could go wrong. Format: [Risk] → Mitigation
|
|
351
|
+
- **Migration Plan**: Steps to deploy, rollback strategy (if applicable)
|
|
352
|
+
- **Open Questions**: Unknowns that can safely be answered later without
|
|
353
|
+
changing the specs, the approach, or the task breakdown. Omit if none.
|
|
354
|
+
|
|
355
|
+
Open questions are for genuinely deferrable unknowns, not decisions you
|
|
356
|
+
skipped. If a question would change the specs, the chosen approach, or
|
|
357
|
+
the task breakdown, resolve it now - ask the user instead of guessing.
|
|
358
|
+
|
|
359
|
+
Focus on architecture and approach, not line-by-line implementation.
|
|
360
|
+
The proposal covers why and what; design covers how. Reference the
|
|
361
|
+
proposal for motivation and, once written, the specs for requirements -
|
|
362
|
+
if a section would only restate them, point to them instead.
|
|
363
|
+
|
|
364
|
+
Good design docs explain the "why" behind technical decisions.
|
|
365
|
+
requires:
|
|
366
|
+
- proposal
|
|
367
|
+
- id: tasks
|
|
368
|
+
generates: tasks.md
|
|
369
|
+
description: Implementation checklist with trackable tasks
|
|
370
|
+
template: tasks.md
|
|
371
|
+
instruction: |
|
|
372
|
+
**Generation (process constraint)**: This artifact is written by exactly
|
|
373
|
+
one author — either YOU, the orchestrating agent (SIMPLE tier), or the
|
|
374
|
+
ONE independent generation subagent dispatched once for the change
|
|
375
|
+
(COMPLEX tier) — never one subagent per artifact, and never by both.
|
|
376
|
+
The full orchestration contract (interview, tier decision, authoring,
|
|
377
|
+
review loop) lives in the clarify artifact's instruction; read it
|
|
378
|
+
first if you have not. The author runs
|
|
379
|
+
`openspec instructions <artifact-id> --change "<name>" --json`, reads
|
|
380
|
+
the project's AGENTS.md and openspec/config.yaml rules plus the
|
|
381
|
+
dependency artifacts (including the clarify.md written by the
|
|
382
|
+
orchestrating agent), then writes to the resolved output path. If a
|
|
383
|
+
generation subagent is the author, it may only read/write inside the
|
|
384
|
+
change directory (openspec/changes/<name>/).
|
|
385
|
+
|
|
386
|
+
Create the task list that breaks down the implementation work.
|
|
387
|
+
|
|
388
|
+
Before writing tasks, check design.md for Open Questions. If any of them
|
|
389
|
+
would change what gets built, resolve them with the user first - do not
|
|
390
|
+
bake an unstated assumption into the task list.
|
|
391
|
+
|
|
392
|
+
**IMPORTANT: Follow the template below exactly.** The apply phase parses
|
|
393
|
+
checkbox format to track progress. Tasks not using `- [ ]` won't be tracked.
|
|
394
|
+
|
|
395
|
+
Guidelines:
|
|
396
|
+
- Group related tasks under `## N. <Task Group Name>` numbered headings
|
|
397
|
+
- Each task MUST be a checkbox: `- [ ] X.Y Task description`
|
|
398
|
+
- Each task SHOULD end with a traceability tag `[spec:<capability>#<requirement>]`
|
|
399
|
+
(or `[infra]` for pure cross-cutting infrastructure tasks). Business tasks
|
|
400
|
+
without a spec target are out of scope - remove them or add the spec
|
|
401
|
+
requirement first.
|
|
402
|
+
- **Related-surface coverage (关联场景覆盖)**: the task groups must cover
|
|
403
|
+
every consuming entry point that the proposal's Related Surface section
|
|
404
|
+
scopes into this change; an in-scope entry point without an adaptation
|
|
405
|
+
task counts as an incomplete task list.
|
|
406
|
+
- **Each task group = one independent iteration.** Every group MUST contain:
|
|
407
|
+
implementation + tests + a quality gate (the project's quality gate per
|
|
408
|
+
openspec/config.yaml rules / AGENTS.md). No standalone QA group - quality
|
|
409
|
+
is embedded in every iteration. Groups must be independently implementable:
|
|
410
|
+
when a group is done, the code compiles and tests pass.
|
|
411
|
+
- **No verification group, pure or mixed.** Pure verification work —
|
|
412
|
+
quality-gate runs (e.g. a final full build verification), end-to-end / integration /
|
|
413
|
+
regression / smoke verification, residual-search sweeps (旧键残留检索),
|
|
414
|
+
regression confirmation — must never form a task group of its own:
|
|
415
|
+
neither standalone, nor as a "wrap-up" group that mixes them with
|
|
416
|
+
doc/script/config sync tasks (e.g. 「阶段二收尾:文档、脚本与全量验证」).
|
|
417
|
+
Fold verification tasks into the functional group they verify. Doc/script/
|
|
418
|
+
config sync tasks are functional work and join the functional group they
|
|
419
|
+
accompany — their presence does not legitimize a wrap-up group. Group
|
|
420
|
+
titles name the delivered business value; 收尾/验证/回归 must never be a
|
|
421
|
+
group's identity.
|
|
422
|
+
- **Minimum substance per group.** Every group's body must be substantive
|
|
423
|
+
change content: at least one task that changes behavior-bearing code or
|
|
424
|
+
config (implementation + its tests). Doc/script/deploy-doc sync,
|
|
425
|
+
verification, regression and quality-gate tasks are accompaniment —
|
|
426
|
+
they may join a functional group but never constitute its substance,
|
|
427
|
+
and task count does not substitute for substance (a 6-task group of
|
|
428
|
+
only sync + verification work still violates this rule). The last
|
|
429
|
+
group of a change is the typical decay spot: when the work remaining
|
|
430
|
+
for a would-be group is only sync + verification, fold it into the
|
|
431
|
+
preceding functional group instead of opening a tail group.
|
|
432
|
+
- Grouping priority (in order):
|
|
433
|
+
1. **Implementation order**: groups follow the project's layer dependency
|
|
434
|
+
order (see AGENTS.md / config.yaml rules). Earlier groups define
|
|
435
|
+
interfaces/models that later groups consume.
|
|
436
|
+
2. **Coherent scope**: each group delivers a complete, verifiable business
|
|
437
|
+
value — a feature, a sub-feature, or a set of closely related capabilities.
|
|
438
|
+
3. **Group size balance**: aim for 4-8 tasks per group. Groups with < 4
|
|
439
|
+
tasks → merge into an adjacent group. Groups with > 8 tasks → split
|
|
440
|
+
into focused sub-groups (each with its own tests and quality gate).
|
|
441
|
+
- Task ordering within a group: dependency chain — define → implement →
|
|
442
|
+
test → quality gate.
|
|
443
|
+
- Tasks should be small enough to complete in one session (~5-10 min coding).
|
|
444
|
+
Implementation details exceeding 10 lines belong in actual code, not as
|
|
445
|
+
separate sub-tasks.
|
|
446
|
+
|
|
447
|
+
Reference specs for what needs to be built, design for how to build it.
|
|
448
|
+
Each task should be verifiable - you know when it's done.
|
|
449
|
+
|
|
450
|
+
**Final report (process constraint)**: When this artifact is written,
|
|
451
|
+
every artifact of the change exists and the authoring session ends
|
|
452
|
+
here. You must NOT dispatch any subagent yourself. If you are the
|
|
453
|
+
generation subagent (COMPLEX tier), your final report MUST end by
|
|
454
|
+
telling the orchestrating agent to start the review loop (Phase 3 of
|
|
455
|
+
the clarify artifact's orchestration contract) - without that handoff
|
|
456
|
+
the review never happens. If you are the orchestrating agent (SIMPLE
|
|
457
|
+
tier), go straight to Phase 3 yourself and dispatch the independent
|
|
458
|
+
review subagent.
|
|
459
|
+
requires:
|
|
460
|
+
- specs
|
|
461
|
+
- design
|
|
462
|
+
apply:
|
|
463
|
+
requires: [tasks]
|
|
464
|
+
tracks: tasks.md
|
|
465
|
+
instruction: |
|
|
466
|
+
Read context files, work through pending tasks, mark complete as you go.
|
|
467
|
+
Follow the project's implementation conventions (AGENTS.md, openspec/config.yaml
|
|
468
|
+
rules, and any orchestration workflow the project uses).
|
|
469
|
+
Pause if you hit blockers or need clarification.
|
package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/clarify.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
## Open Questions
|
|
2
|
+
|
|
3
|
+
<!-- Unknowns surfaced during clarification. One per bullet. Cover: problem & intent,
|
|
4
|
+
scope, related-surface survey, data model, business rules, integration
|
|
5
|
+
boundaries, architecture approaches. Resolve them with the user by
|
|
6
|
+
interviewing in rounds over a decision tree: each round asks every currently
|
|
7
|
+
answerable question (numbered, each with a recommended answer); keep going
|
|
8
|
+
until no doubt is left unresolved and nothing is silently assumed. -->
|
|
9
|
+
|
|
10
|
+
- <!-- question -->
|
|
11
|
+
|
|
12
|
+
## User Answers
|
|
13
|
+
|
|
14
|
+
<!-- The user's responses, captured verbatim or summarized. -->
|
|
15
|
+
|
|
16
|
+
- <!-- answer -->
|
|
17
|
+
|
|
18
|
+
## Related-Surface Survey
|
|
19
|
+
|
|
20
|
+
<!-- 关联场景清点:every consumer of the capability being changed, grounded in
|
|
21
|
+
code evidence (file/class/method): functional entry points (API endpoints,
|
|
22
|
+
import/export entries, scheduled jobs, message consumers, frontend calls),
|
|
23
|
+
shared processing logic reused by multiple entry points, and downstream
|
|
24
|
+
data consumers (readers/writers of the tables, fields, events, or export
|
|
25
|
+
artifacts involved). When shared processing logic changes, every consuming
|
|
26
|
+
entry point must be adapted in the SAME change; splitting into separate
|
|
27
|
+
changes requires an explicit boundary rationale confirmed by the user. -->
|
|
28
|
+
|
|
29
|
+
- <!-- entry point / shared logic / downstream consumer + code evidence + scoping verdict -->
|
|
30
|
+
|
|
31
|
+
## Architecture Direction
|
|
32
|
+
|
|
33
|
+
<!-- The crystallized approach: chosen direction, key decisions, 2-3 alternatives
|
|
34
|
+
considered with tradeoffs, and the plan summary the user explicitly confirmed
|
|
35
|
+
(interview answers alone do not count as confirmation; the summary must be a
|
|
36
|
+
plain message listing the key points — never a question-tool prompt). This is
|
|
37
|
+
the HARD-GATE output — proposal, specs, design, and tasks must stay consistent
|
|
38
|
+
with what is recorded here. -->
|
|
39
|
+
|
|
40
|
+
## Authoring Tier
|
|
41
|
+
|
|
42
|
+
<!-- SIMPLE or COMPLEX, with a one-line rationale (criteria in the clarify
|
|
43
|
+
instruction's Phase 2). SIMPLE: orchestrating agent writes proposal →
|
|
44
|
+
specs → design → tasks itself. COMPLEX: one generation subagent writes
|
|
45
|
+
them. Recorded here for auditability before Phase 2 starts. -->
|
|
46
|
+
|
|
47
|
+
- Tier: <!-- SIMPLE / COMPLEX -->
|
|
48
|
+
- Rationale: <!-- one line -->
|
|
49
|
+
|
|
50
|
+
## Initial Capabilities
|
|
51
|
+
|
|
52
|
+
<!-- Preliminary guess of which specs will be created or modified (kebab-case names).
|
|
53
|
+
Finalized in proposal.md Capabilities section. -->
|
|
54
|
+
|
|
55
|
+
- `<name>`: <!-- what this capability covers -->
|
package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/design.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Context
|
|
2
|
+
|
|
3
|
+
<!-- Background and current state -->
|
|
4
|
+
|
|
5
|
+
## Goals / Non-Goals
|
|
6
|
+
|
|
7
|
+
**Goals:**
|
|
8
|
+
<!-- What this design aims to achieve -->
|
|
9
|
+
|
|
10
|
+
**Non-Goals:**
|
|
11
|
+
<!-- What is explicitly out of scope -->
|
|
12
|
+
|
|
13
|
+
## Decisions
|
|
14
|
+
|
|
15
|
+
<!-- Key design decisions and rationale. For each: the choice, why, and alternatives considered. -->
|
|
16
|
+
|
|
17
|
+
## Data Model
|
|
18
|
+
|
|
19
|
+
<!-- MANDATORY when the change involves persistence. Complete DDL: CREATE TABLE with
|
|
20
|
+
columns, types, constraints; field descriptions; indexes; audit fields.
|
|
21
|
+
Follow the project's persistence conventions (AGENTS.md / openspec/config.yaml
|
|
22
|
+
rules: table prefix, primary key strategy, logical delete, audit columns).
|
|
23
|
+
If no persistence, state that explicitly. -->
|
|
24
|
+
|
|
25
|
+
## Risks / Trade-offs
|
|
26
|
+
|
|
27
|
+
<!-- Known risks and trade-offs. Format: [Risk] → Mitigation -->
|
|
28
|
+
|
|
29
|
+
## Open Questions
|
|
30
|
+
|
|
31
|
+
<!-- Outstanding decisions or unknowns to resolve -->
|
package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/proposal.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
## Why
|
|
2
|
+
|
|
3
|
+
<!-- Explain the motivation for this change. What problem does this solve? Why now? -->
|
|
4
|
+
|
|
5
|
+
## What Changes
|
|
6
|
+
|
|
7
|
+
<!-- Describe what will change. Be specific about new capabilities, modifications, or removals. -->
|
|
8
|
+
|
|
9
|
+
## Related Surface
|
|
10
|
+
|
|
11
|
+
<!-- 关联场景:survey conclusions from clarify.md — each entry point / shared
|
|
12
|
+
processing logic / downstream consumer with code evidence and its scoping
|
|
13
|
+
verdict (adapted in this change, or explicitly justified out with a boundary
|
|
14
|
+
rationale confirmed by the user). Shared processing logic changes must cover
|
|
15
|
+
all consuming entry points in this change. A bare "no related impact"
|
|
16
|
+
without search evidence counts as an incomplete proposal. -->
|
|
17
|
+
|
|
18
|
+
## Capabilities
|
|
19
|
+
|
|
20
|
+
### New Capabilities
|
|
21
|
+
<!-- Capabilities being introduced. Replace <name> with kebab-case identifier (e.g., user-auth, data-export, api-rate-limiting). Each creates specs/<name>/spec.md -->
|
|
22
|
+
- `<name>`: <brief description of what this capability covers>
|
|
23
|
+
|
|
24
|
+
### Modified Capabilities
|
|
25
|
+
<!-- Existing capabilities whose REQUIREMENTS are changing (not just implementation).
|
|
26
|
+
Only list here if spec-level behavior changes. Each needs a delta spec file.
|
|
27
|
+
Use existing spec names from openspec/specs/. Leave empty if no requirement changes. -->
|
|
28
|
+
- `<existing-name>`: <what requirement is changing>
|
|
29
|
+
|
|
30
|
+
## Impact
|
|
31
|
+
|
|
32
|
+
<!-- Affected code, APIs, dependencies, systems -->
|
package/assets/skills/openspec-standard/templates/schemas/spec-driven-standard/templates/tasks.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
模板说明:每个 ## N. 是一个独立迭代(task group),编排工具会逐个处理。
|
|
3
|
+
每个迭代必须自带质量保证:实现代码 + 测试 + 项目质量门(见 AGENTS.md)。
|
|
4
|
+
每组必须以实质变更为主体:至少一个任务承载真实的代码/配置行为变更(实现 + 测试)。
|
|
5
|
+
任务组划分必须覆盖 proposal「关联场景」小节划入本 change 的全部消费入口。
|
|
6
|
+
禁止把纯验证工作(质量门、端到端/回归/冒烟/残留检索)设为独立
|
|
7
|
+
任务组——无论单独成组还是混入文档/脚本同步拼成「收尾验证组」;文档/脚本同步
|
|
8
|
+
与验证只是功能组的随行任务,末位剩余工作只剩同步与验证时并入前一个功能组,
|
|
9
|
+
禁止新开收尾组,组标题以交付的业务价值命名。
|
|
10
|
+
|
|
11
|
+
> **各组**可独立实施,组内全部完成后编译通过、可启动、测试通过。
|
|
12
|
+
> **序号**即实施顺序——跨组依赖确保上游组提交后下游组可对接。
|
|
13
|
+
> 每个源文件在 tasks.md 中最多出现**一次**(避免跨组并行修改冲突)。
|
|
14
|
+
-->
|
|
15
|
+
|
|
16
|
+
> **前置 change:** 本 change 依赖 <name> change 完成后才可开始。参见 openspec/changes/<name>/。
|
|
17
|
+
|
|
18
|
+
## 1. <迭代名称:业务价值或功能名称>
|
|
19
|
+
|
|
20
|
+
<!-- 本组 4-8 个任务。独立迭代:实现 + 测试 + 质量门。
|
|
21
|
+
组内任务按依赖链排列:定义 → 实现 → 测试 → 质量门。
|
|
22
|
+
每个任务以 [spec:<capability>#<requirement>] 标注追溯(纯横切基础设施任务标 [infra])。 -->
|
|
23
|
+
|
|
24
|
+
- [ ] 1.1 <!-- 任务描述 --> [spec:<capability>#<requirement>]
|
|
25
|
+
- [ ] 1.2 <!-- 任务描述 --> [spec:<capability>#<requirement>]
|
|
26
|
+
- [ ] 1.3 <!-- 任务描述 --> [spec:<capability>]
|
|
27
|
+
- [ ] 1.4 <!-- 任务描述 --> [spec:<capability>]
|
|
28
|
+
- [ ] 1.5 <!-- 测试:UT + IT --> [spec:<capability>]
|
|
29
|
+
- [ ] 1.6 <!-- 质量门:项目质量门通过(见 AGENTS.md) --> [infra]
|
|
30
|
+
|
|
31
|
+
## 2. <迭代名称:业务价值或功能名称>
|
|
32
|
+
|
|
33
|
+
<!-- 本组 4-8 个任务。独立迭代:实现 + 测试 + 质量门。
|
|
34
|
+
组内任务按依赖链排列:定义 → 实现 → 测试 → 质量门。 -->
|
|
35
|
+
|
|
36
|
+
- [ ] 2.1 <!-- 任务描述 --> [spec:<capability>#<requirement>]
|
|
37
|
+
- [ ] 2.2 <!-- 任务描述 --> [spec:<capability>#<requirement>]
|
|
38
|
+
- [ ] 2.3 <!-- 任务描述 --> [spec:<capability>]
|
|
39
|
+
- [ ] 2.4 <!-- 任务描述 --> [spec:<capability>]
|
|
40
|
+
- [ ] 2.5 <!-- 测试 --> [spec:<capability>]
|
|
41
|
+
- [ ] 2.6 <!-- 质量门 --> [infra]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ifrankwang/openspec-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.136.0",
|
|
4
4
|
"description": "OpenSpec change orchestration plugin for AI coding agents (Claude Code, Codex, ZCode, DeepSeek Harness, OpenCode)",
|
|
5
5
|
"author": "ifrankwang",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,7 +55,7 @@ const TOOL_SPECS: Record<string, ToolSpec> = {
|
|
|
55
55
|
},
|
|
56
56
|
opx_orch_complete_task_group: {
|
|
57
57
|
description:
|
|
58
|
-
"完成任务组收尾:合并 task-group 分支到 baseBranch → 清理 worktree 与分支。须在收尾验证(verify_cleanup
|
|
58
|
+
"完成任务组收尾:合并 task-group 分支到 baseBranch → 清理 worktree 与分支。须在收尾验证(verify_cleanup)通过后调用。合并冲突、主仓库本地改动文件与合并写入文件重合、或存在部分暂存文件时中止并返回 blocked(保留 worktree/分支);主仓库无关脏文件不阻塞合并。",
|
|
59
59
|
schema: completeTaskGroupSchema,
|
|
60
60
|
execute: (args, ctx) => completeTaskGroupExecute(args as any, ctx),
|
|
61
61
|
},
|
package/src/core/git.ts
CHANGED
|
@@ -216,19 +216,86 @@ export async function markTaskGroupCheckboxesComplete(
|
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/** 单条 `git status --porcelain` 条目:XY 状态码 + 涉及路径(重命名条目含 old/new 双路径)。 */
|
|
220
|
+
interface StatusEntry {
|
|
221
|
+
x: string
|
|
222
|
+
y: string
|
|
223
|
+
paths: string[]
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** 去除 porcelain/diff 输出中路径的包裹引号(特殊字符路径会被 git 以双引号包裹)。 */
|
|
227
|
+
function unquotePath(p: string): string {
|
|
228
|
+
const t = p.trim()
|
|
229
|
+
return t.startsWith('"') && t.endsWith('"') && t.length >= 2 ? t.slice(1, -1) : t
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 解析 `git status --porcelain` 为结构化条目(独立于 parsePorcelainPaths,后者仅取新路径且被
|
|
234
|
+
* detectChanges / reconcileMainPollution 依赖,语义不可原地变更)。
|
|
235
|
+
* 重命名条目(`R old -> new`)双向路径都计入——两侧路径都可能与合并写入清单重合。
|
|
236
|
+
*
|
|
237
|
+
* 注意:GitRunner 的 run/runChecked 都对 stdout 整体 trim(),porcelain 首行若以空格开头(X=' ' 的
|
|
238
|
+
* 未暂存条目)会被吃掉前导空格、列位整体左移一格。porcelain v1 的位置 2 恒为分隔空格,据此识别
|
|
239
|
+
* 被破坏的首行并补正。
|
|
240
|
+
*/
|
|
241
|
+
function parseStatusEntries(out: string): StatusEntry[] {
|
|
242
|
+
const entries: StatusEntry[] = []
|
|
243
|
+
for (const raw of out.split("\n")) {
|
|
244
|
+
if (raw.length < 4) continue
|
|
245
|
+
let line = raw
|
|
246
|
+
if (line[2] !== " ") {
|
|
247
|
+
// 位置 2 非分隔空格 = 首行被整体 trim 破坏(X=' ' 条目吃掉前导空格,列位左移一格)
|
|
248
|
+
if (line[1] !== " ") continue
|
|
249
|
+
line = ` ${line}`
|
|
250
|
+
}
|
|
251
|
+
if (line[0] === " " && line[1] === " ") continue
|
|
252
|
+
const pathPart = line.slice(3)
|
|
253
|
+
const arrow = pathPart.indexOf(" -> ")
|
|
254
|
+
const paths = arrow >= 0
|
|
255
|
+
? [unquotePath(pathPart.slice(0, arrow)), unquotePath(pathPart.slice(arrow + 4))]
|
|
256
|
+
: [unquotePath(pathPart)]
|
|
257
|
+
if (paths.every((p) => p !== "")) entries.push({ x: line[0], y: line[1], paths })
|
|
258
|
+
}
|
|
259
|
+
return entries
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** 解析 `git diff --name-only` 输出为路径列表(含引号路径解包)。 */
|
|
263
|
+
function parseDiffNameOnly(out: string): string[] {
|
|
264
|
+
return out.split("\n").map(unquotePath).filter(Boolean)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 两条路径是否重合(脏文件 ↔ 合并将写入文件):尾斜杠归一后相等(未跟踪目录 `dir/` 与文件 `dir`
|
|
269
|
+
* 同名冲突),或互为路径段前缀(未跟踪目录 `dir/` 覆盖目录下任意写入路径,双向判定)。
|
|
270
|
+
*/
|
|
271
|
+
function pathsOverlap(a: string, b: string): boolean {
|
|
272
|
+
const na = a.replace(/\/+$/, "")
|
|
273
|
+
const nb = b.replace(/\/+$/, "")
|
|
274
|
+
if (!na || !nb) return false
|
|
275
|
+
if (na === nb) return true
|
|
276
|
+
return na.startsWith(`${nb}/`) || nb.startsWith(`${na}/`)
|
|
277
|
+
}
|
|
278
|
+
|
|
219
279
|
/**
|
|
220
280
|
* 收尾合并:将任务组分支合并进基础分支。
|
|
221
281
|
*
|
|
222
282
|
* 先解析主仓库根(后续命令一律以主仓库根为 -C 目标,不沿用入参目录),再按目标分支的检出状态分流:
|
|
223
|
-
* 1. `merge-base --is-ancestor`
|
|
224
|
-
*
|
|
283
|
+
* 1. `merge-base --is-ancestor` 幂等检查(重试幂等根基,禁止移除):源分支已是目标分支祖先
|
|
284
|
+
* (如人工解决冲突后重调)直接成功,不产生空合并提交;
|
|
225
285
|
* 2. 检出检测(`worktree list --porcelain`,按目标分支匹配 `branch refs/heads/<X>` 行):
|
|
226
|
-
* - 目标分支正被其它 linked worktree
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* -
|
|
230
|
-
*
|
|
231
|
-
*
|
|
286
|
+
* - 目标分支正被其它 linked worktree 检出 → 返回 blockedMessage(未执行任何 git 变更动作,
|
|
287
|
+
* 由上层转 blocked 文案)——单靠 update-ref 推进分支指针会使检出方暂存区/工作区与分支引用失步
|
|
288
|
+
* (status 显示整套反向改动);
|
|
289
|
+
* - 目标分支正被主仓库检出(无论是否干净):先 `merge-tree --write-tree` 内存拟合并
|
|
290
|
+
* (零副作用;退出码 1 = 冲突,按冲突形态返回),无冲突时以拟合并产出树精确计算
|
|
291
|
+
* 「合并将写入的文件清单」,与主仓库脏文件(已暂存/未暂存/未跟踪;重命名双向路径;
|
|
292
|
+
* 未跟踪目录按路径段前缀双向匹配)求重合后分流:
|
|
293
|
+
* * 有重合 → blocked(列出重合文件;真实合并会覆盖本地改动,交由人工处理);
|
|
294
|
+
* * 无重合且存在已暂存改动 → 完整暂存(暂存内容与工作区一致)时走无损路径:
|
|
295
|
+
* `restore --staged` → 真实合并 → finally `git add` 还原暂存态(合并不触碰这些文件);
|
|
296
|
+
* 部分暂存(同一文件既有已暂存又有未暂存改动)→ blocked(自动操作会破坏暂存粒度);
|
|
297
|
+
* * 无重合且仅未暂存/未跟踪脏 → 直接真实 `git merge --no-ff`(git 对无关脏文件安全保留;
|
|
298
|
+
* 失败时 `merge --abort` 兜底恢复);
|
|
232
299
|
* 3. 无人检出(worktreeless 底层命令,主仓库工作区状态不受影响;冲突时目标分支引用未动、磁盘无半成品):
|
|
233
300
|
* `merge-tree --write-tree`(需 git ≥ 2.38)内存试算合并树:退出码 1 = 冲突,直接返回;
|
|
234
301
|
* `commit-tree` 生成双父合并提交,`update-ref` 以试算前目标分支 oid 作旧值校验推进(CAS,防并发覆盖)。
|
|
@@ -261,17 +328,6 @@ export async function mergeBranchToTarget(
|
|
|
261
328
|
].join("\n"),
|
|
262
329
|
}
|
|
263
330
|
}
|
|
264
|
-
const status = await runGit(mainRepo, ["status", "--porcelain"])
|
|
265
|
-
if (status.trim().length > 0) {
|
|
266
|
-
return {
|
|
267
|
-
success: false,
|
|
268
|
-
conflict: false,
|
|
269
|
-
blockedMessage: [
|
|
270
|
-
`- **原因**: 目标分支 \`${targetBranch}\` 正被主仓库检出,且主仓库存在未提交改动;为避免覆盖本地改动,本次未执行任何合并动作(分支引用未动、无半成品)。`,
|
|
271
|
-
`- **处理**: 请先 commit 或 stash 主仓库改动后重试;或自行执行 \`git merge ${sourceBranch}\`(真实合并自带脏工作区保护)。`,
|
|
272
|
-
].join("\n"),
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
331
|
const mergeTree = await runGitChecked(mainRepo, ["merge-tree", "--write-tree", "--messages", targetBranch, sourceBranch])
|
|
276
332
|
if (!mergeTree.success) {
|
|
277
333
|
// merge-tree 退出码 1 = 冲突;其余为分支缺失等真实错误
|
|
@@ -280,9 +336,76 @@ export async function mergeBranchToTarget(
|
|
|
280
336
|
}
|
|
281
337
|
return { success: false, conflict: true }
|
|
282
338
|
}
|
|
339
|
+
const mergedTreeOid = mergeTree.stdout.split("\n")[0].trim()
|
|
340
|
+
|
|
341
|
+
const statusRes = await runGitChecked(mainRepo, ["status", "--porcelain"])
|
|
342
|
+
if (!statusRes.success) {
|
|
343
|
+
throw new Error(`无法读取主仓库工作区状态:${statusRes.stderr}`)
|
|
344
|
+
}
|
|
345
|
+
const entries = parseStatusEntries(statusRes.stdout)
|
|
346
|
+
|
|
347
|
+
if (entries.length > 0) {
|
|
348
|
+
const dirtyPaths = [...new Set(entries.flatMap((e) => e.paths))]
|
|
349
|
+
const written = parseDiffNameOnly(
|
|
350
|
+
await runGit(mainRepo, ["diff", "--name-only", "--no-renames", targetBranch, mergedTreeOid]),
|
|
351
|
+
)
|
|
352
|
+
const overlap = written.filter((w) => dirtyPaths.some((d) => pathsOverlap(d, w)))
|
|
353
|
+
if (overlap.length > 0) {
|
|
354
|
+
return {
|
|
355
|
+
success: false,
|
|
356
|
+
conflict: false,
|
|
357
|
+
blockedMessage: [
|
|
358
|
+
`- **原因**: 目标分支 \`${targetBranch}\` 正被主仓库检出,且以下本地改动文件与任务组合并将写入的文件重合;为避免覆盖本地改动,本次未执行任何合并动作(分支引用未动、无半成品):`,
|
|
359
|
+
...overlap.map((f) => ` - \`${f}\``),
|
|
360
|
+
`- **处理**: 请先 commit/stash/移除上述文件的本地改动后重试;或自行执行 \`git merge ${sourceBranch}\`(真实合并自带脏工作区保护)。`,
|
|
361
|
+
].join("\n"),
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 已暂存条目:X 列非空且非 ?(?? 为未跟踪);重命名双向路径都须纳入还原
|
|
366
|
+
const stagedPaths = [...new Set(entries.filter((e) => e.x !== " " && e.x !== "?").flatMap((e) => e.paths))]
|
|
367
|
+
if (stagedPaths.length > 0) {
|
|
368
|
+
// 完整暂存判定:暂存区与工作区无差异。git merge 对 index 相对 HEAD 的任何差异一律拒绝
|
|
369
|
+
// (与合并是否重合无关),需先取消暂存绕行;部分暂存场景自动还原会丢失暂存粒度,拒绝代劳。
|
|
370
|
+
const unstagedDelta = await runGit(mainRepo, ["diff", "--name-only", "--", ...stagedPaths])
|
|
371
|
+
if (parseDiffNameOnly(unstagedDelta).length > 0) {
|
|
372
|
+
return {
|
|
373
|
+
success: false,
|
|
374
|
+
conflict: false,
|
|
375
|
+
blockedMessage: [
|
|
376
|
+
`- **原因**: 主仓库存在部分暂存文件(同一文件既有已暂存又有未暂存改动),自动合并会改变暂存粒度;本次未执行任何合并动作(分支引用未动、无半成品)。`,
|
|
377
|
+
`- **处理**: 请先 commit 或 stash,或补全/取消暂存后重试;或自行执行 \`git merge ${sourceBranch}\`。`,
|
|
378
|
+
].join("\n"),
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// 无损路径:取消暂存 → 真实合并(不触碰这些文件)→ 兜底重新暂存还原
|
|
382
|
+
let unstagedByUs = false
|
|
383
|
+
try {
|
|
384
|
+
const restore = await runGitChecked(mainRepo, ["restore", "--staged", "--", ...stagedPaths])
|
|
385
|
+
if (!restore.success) throw new Error(`git restore --staged 失败(未执行合并):${restore.stderr}`)
|
|
386
|
+
unstagedByUs = true
|
|
387
|
+
const mergeResult = await runGitChecked(mainRepo, ["merge", "--no-ff", sourceBranch])
|
|
388
|
+
if (!mergeResult.success) {
|
|
389
|
+
// 理论上仅竞态会走到此分支:兜底中止,避免主仓库停留在合并中间态
|
|
390
|
+
await runGitChecked(mainRepo, ["merge", "--abort"])
|
|
391
|
+
return { success: false, conflict: true }
|
|
392
|
+
}
|
|
393
|
+
return { success: true, conflict: false }
|
|
394
|
+
} finally {
|
|
395
|
+
if (unstagedByUs) {
|
|
396
|
+
const readd = await runGitChecked(mainRepo, ["add", "--", ...stagedPaths])
|
|
397
|
+
if (!readd.success) {
|
|
398
|
+
// 暂存态还原失败不可静默:合并已发生,抛错让上层感知(重试时 is-ancestor 幂等短路)
|
|
399
|
+
throw new Error(`合并后还原暂存态失败(git add):${readd.stderr};请人工核对这些文件的暂存状态`)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
283
406
|
const mergeResult = await runGitChecked(mainRepo, ["merge", "--no-ff", sourceBranch])
|
|
284
407
|
if (!mergeResult.success) {
|
|
285
|
-
//
|
|
408
|
+
// 兜底中止,避免主仓库停留在合并中间态(无重合脏文件时 git 自身保护仍会拒绝越界写入)
|
|
286
409
|
await runGitChecked(mainRepo, ["merge", "--abort"])
|
|
287
410
|
return { success: false, conflict: true }
|
|
288
411
|
}
|