@ifrankwang/openspec-agents 0.149.0 → 0.150.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 +73 -19
- package/README.md +92 -59
- package/assets/skills/orchestrator/SKILL.md +9 -1
- package/dsh/cordis.patch.yml +320 -220
- package/package.json +2 -2
- package/src/adapters/codex/index.ts +2 -2
- package/src/adapters/deepseek-harness/index.ts +118 -26
- package/src/adapters/deepseek-harness/native-tools.ts +3 -3
- package/src/adapters/plugin-common/index.ts +1 -1
- package/src/types/dsh-runtime.d.ts +1 -1
package/.mcp-server/cli.mjs
CHANGED
|
@@ -3284,6 +3284,37 @@ var require_utils = __commonJS(function(exports, module) {
|
|
|
3284
3284
|
}
|
|
3285
3285
|
return output.join("");
|
|
3286
3286
|
}
|
|
3287
|
+
function removeDotSegmentsRelative(path) {
|
|
3288
|
+
if (path === "" || path[0] === "/") {
|
|
3289
|
+
return removeDotSegments(path);
|
|
3290
|
+
}
|
|
3291
|
+
const segments = path.split("/");
|
|
3292
|
+
const last = segments.length - 1;
|
|
3293
|
+
const output = [];
|
|
3294
|
+
let trailingDirectory = false;
|
|
3295
|
+
for (let i = 0;i <= last; i++) {
|
|
3296
|
+
const segment = segments[i];
|
|
3297
|
+
if (segment === "." || segment === "..") {
|
|
3298
|
+
if (segment === "..") {
|
|
3299
|
+
if (output.length !== 0 && output[output.length - 1] !== "..") {
|
|
3300
|
+
output.pop();
|
|
3301
|
+
} else {
|
|
3302
|
+
output.push("..");
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
trailingDirectory = i === last;
|
|
3306
|
+
} else {
|
|
3307
|
+
output.push(segment);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
if (trailingDirectory && output[output.length - 1] !== "..") {
|
|
3311
|
+
output.push("");
|
|
3312
|
+
}
|
|
3313
|
+
if (output.length === 0 || output[0] === "" || output[0].includes(":")) {
|
|
3314
|
+
output.unshift(".");
|
|
3315
|
+
}
|
|
3316
|
+
return output.join("/");
|
|
3317
|
+
}
|
|
3287
3318
|
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3288
3319
|
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3289
3320
|
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
@@ -3544,6 +3575,7 @@ var require_utils = __commonJS(function(exports, module) {
|
|
|
3544
3575
|
encodeFragment,
|
|
3545
3576
|
escapePreservingEscapes,
|
|
3546
3577
|
removeDotSegments,
|
|
3578
|
+
removeDotSegmentsRelative,
|
|
3547
3579
|
isIPv4,
|
|
3548
3580
|
isUUID,
|
|
3549
3581
|
normalizeIPv6,
|
|
@@ -3728,16 +3760,18 @@ var require_schemes = __commonJS(function(exports, module) {
|
|
|
3728
3760
|
|
|
3729
3761
|
// node_modules/fast-uri/index.js
|
|
3730
3762
|
var require_fast_uri = __commonJS(function(exports, module) {
|
|
3731
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3763
|
+
var { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3732
3764
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3733
3765
|
var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
|
|
3734
3766
|
var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3767
|
+
var MALFORMED_HOST_ERROR = "URI host is malformed.";
|
|
3768
|
+
var FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
|
|
3769
|
+
function validateScheme(scheme) {
|
|
3770
|
+
const stringScheme = String(scheme);
|
|
3771
|
+
if (!VALID_SCHEME.test(stringScheme)) {
|
|
3738
3772
|
throw new TypeError(MALFORMED_SCHEME_ERROR);
|
|
3739
3773
|
}
|
|
3740
|
-
return
|
|
3774
|
+
return stringScheme;
|
|
3741
3775
|
}
|
|
3742
3776
|
function normalize(uri, options) {
|
|
3743
3777
|
if (typeof uri === "string") {
|
|
@@ -3772,6 +3806,9 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
3772
3806
|
const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
|
|
3773
3807
|
const resolvedHost = resolved.host;
|
|
3774
3808
|
const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
|
|
3809
|
+
if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
|
|
3810
|
+
throw new Error(MALFORMED_HOST_ERROR);
|
|
3811
|
+
}
|
|
3775
3812
|
canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
|
|
3776
3813
|
const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
|
|
3777
3814
|
if (resolved.error && !encodedASCIIHost) {
|
|
@@ -3813,14 +3850,15 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
3813
3850
|
if (relative.path[0] === "/") {
|
|
3814
3851
|
target.path = removeDotSegments(relative.path);
|
|
3815
3852
|
} else {
|
|
3816
|
-
|
|
3853
|
+
const baseHasAuthority = base.userinfo !== undefined || base.host !== undefined || base.port !== undefined;
|
|
3854
|
+
if (baseHasAuthority && !base.path) {
|
|
3817
3855
|
target.path = "/" + relative.path;
|
|
3818
3856
|
} else if (!base.path) {
|
|
3819
3857
|
target.path = relative.path;
|
|
3820
3858
|
} else {
|
|
3821
3859
|
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
|
|
3822
3860
|
}
|
|
3823
|
-
target.path = removeDotSegments(target.path);
|
|
3861
|
+
target.path = base.scheme === undefined && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
|
|
3824
3862
|
}
|
|
3825
3863
|
target.query = relative.query;
|
|
3826
3864
|
}
|
|
@@ -3858,13 +3896,14 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
3858
3896
|
const options = Object.assign({}, opts);
|
|
3859
3897
|
const uriTokens = [];
|
|
3860
3898
|
if (component.scheme) {
|
|
3861
|
-
component.scheme =
|
|
3899
|
+
component.scheme = validateScheme(component.scheme);
|
|
3862
3900
|
}
|
|
3863
3901
|
const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
|
|
3864
3902
|
if (schemeHandler && schemeHandler.serialize)
|
|
3865
3903
|
schemeHandler.serialize(component, options);
|
|
3866
3904
|
const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined;
|
|
3867
|
-
const
|
|
3905
|
+
const schemelessReference = (component.scheme === undefined || options.reference === "suffix") && !hasAuthority;
|
|
3906
|
+
const pathNoScheme = !options.skipEscape && schemelessReference;
|
|
3868
3907
|
if (component.path !== undefined) {
|
|
3869
3908
|
if (!options.skipEscape) {
|
|
3870
3909
|
component.path = serializePathEncoding(component.path, pathNoScheme);
|
|
@@ -3873,7 +3912,7 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
3873
3912
|
}
|
|
3874
3913
|
}
|
|
3875
3914
|
if (options.reference !== "suffix" && component.scheme) {
|
|
3876
|
-
component.scheme =
|
|
3915
|
+
component.scheme = validateScheme(component.scheme);
|
|
3877
3916
|
uriTokens.push(component.scheme, ":");
|
|
3878
3917
|
}
|
|
3879
3918
|
const authority = recomposeAuthority(component);
|
|
@@ -3889,9 +3928,9 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
3889
3928
|
if (component.path !== undefined) {
|
|
3890
3929
|
let s = component.path;
|
|
3891
3930
|
if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
|
|
3892
|
-
s = removeDotSegments(s);
|
|
3931
|
+
s = schemelessReference && s[0] !== "/" ? removeDotSegmentsRelative(s) : removeDotSegments(s);
|
|
3893
3932
|
}
|
|
3894
|
-
if (
|
|
3933
|
+
if (schemelessReference) {
|
|
3895
3934
|
s = serializePathEncoding(s, true);
|
|
3896
3935
|
}
|
|
3897
3936
|
if (authority === undefined && s[0] === "/" && s[1] === "/") {
|
|
@@ -3939,16 +3978,32 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
3939
3978
|
return hasMalformedPercentEncoding(matches[3]) || host !== undefined && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
|
|
3940
3979
|
}
|
|
3941
3980
|
function canonicalizeHost(parsed, options, schemeHandler, isIP) {
|
|
3981
|
+
if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
|
|
3982
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
3983
|
+
return true;
|
|
3984
|
+
}
|
|
3942
3985
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
3986
|
+
let url;
|
|
3943
3987
|
try {
|
|
3944
|
-
|
|
3988
|
+
url = new URL("http://" + parsed.host);
|
|
3945
3989
|
} catch (e) {
|
|
3946
3990
|
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
3947
3991
|
return true;
|
|
3948
3992
|
}
|
|
3993
|
+
if (!isHostOnlyURL(url)) {
|
|
3994
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
3995
|
+
return true;
|
|
3996
|
+
}
|
|
3997
|
+
parsed.host = url.hostname;
|
|
3949
3998
|
}
|
|
3950
3999
|
return false;
|
|
3951
4000
|
}
|
|
4001
|
+
function hasForbiddenDomainHost(parsed, options, schemeHandler, isIP) {
|
|
4002
|
+
return Boolean(parsed.host && isIP === false && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host));
|
|
4003
|
+
}
|
|
4004
|
+
function isHostOnlyURL(url) {
|
|
4005
|
+
return url.username === "" && url.password === "" && url.port === "" && url.pathname === "/" && url.search === "" && url.hash === "" && url.hostname !== "";
|
|
4006
|
+
}
|
|
3952
4007
|
function parseWithStatus(uri, opts) {
|
|
3953
4008
|
const options = Object.assign({}, opts);
|
|
3954
4009
|
const parsed = {
|
|
@@ -4003,9 +4058,8 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
4003
4058
|
parsed.query = matches[7];
|
|
4004
4059
|
parsed.fragment = matches[8];
|
|
4005
4060
|
if (parsed.scheme !== undefined) {
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
parsed.scheme = decodedScheme.toLowerCase();
|
|
4061
|
+
if (VALID_SCHEME.test(parsed.scheme)) {
|
|
4062
|
+
parsed.scheme = parsed.scheme.toLowerCase();
|
|
4009
4063
|
} else {
|
|
4010
4064
|
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
|
|
4011
4065
|
malformedScheme = true;
|
|
@@ -4033,7 +4087,7 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
4033
4087
|
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
|
|
4034
4088
|
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
|
|
4035
4089
|
if (malformedIPLiteral) {
|
|
4036
|
-
parsed.error = parsed.error ||
|
|
4090
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
4037
4091
|
malformedAuthorityOrPort = true;
|
|
4038
4092
|
}
|
|
4039
4093
|
} else {
|
|
@@ -4061,7 +4115,7 @@ var require_fast_uri = __commonJS(function(exports, module) {
|
|
|
4061
4115
|
if (!isIP) {
|
|
4062
4116
|
host = normalizePercentEncoding(host.toLowerCase());
|
|
4063
4117
|
}
|
|
4064
|
-
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4118
|
+
parsed.host = isIP || options.scheme !== "null" ? reescapeHostDelimiters(host, isIP) : host;
|
|
4065
4119
|
}
|
|
4066
4120
|
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4067
4121
|
if (parsed.path) {
|
|
@@ -30575,7 +30629,7 @@ async function ensureDefaultUnattended(args, ctx) {
|
|
|
30575
30629
|
}
|
|
30576
30630
|
} catch {}
|
|
30577
30631
|
}
|
|
30578
|
-
var PKG_VERSION = "0.
|
|
30632
|
+
var PKG_VERSION = "0.150.0";
|
|
30579
30633
|
function buildMcpServer(worktree, opts = {}) {
|
|
30580
30634
|
const mcp = new McpServer({ name: "openspec-agents", version: PKG_VERSION });
|
|
30581
30635
|
for (const [name, spec] of Object.entries(TOOL_SPECS)) {
|
package/README.md
CHANGED
|
@@ -3,34 +3,49 @@
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
`openspec-agents` 是 [OpenSpec](https://openspec.sh) 的配套插件,兼容 Claude Code / Codex / ZCode / DeepSeek Harness / OpenCode:提案阶段把标准钉得更死(澄清在前、改动范围与契约变更逐条确认),实施阶段由多 Agent 分工——写、查、验收、质量审查各由不同 Agent 担任,按生产级标准把控到收口。
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
前提:项目正在使用 OpenSpec 管理需求。
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
| | |
|
|
11
|
+
|---|---|
|
|
12
|
+
| 适合 | 变更需要真正落进代码库、有验收标准、需通过检查、过程可追溯的团队与项目 |
|
|
13
|
+
| 不适合 | 希望快速产出 demo、不走检查与验收的场景 |
|
|
14
|
+
| 代价 | 比单个 AI 直接修改更慢,Token 消耗更高 |
|
|
15
|
+
| 模型选型 | 实施用 flash 类模型,提案用旗舰模型 |
|
|
11
16
|
|
|
12
|
-
##
|
|
17
|
+
## 常见痛点
|
|
13
18
|
|
|
14
|
-
- AI
|
|
15
|
-
-
|
|
16
|
-
-
|
|
19
|
+
- AI 一次改一大片,改完声称「完成了」——能编译、测试也通过,但其中夹带的空实现与伪成功未被发现。
|
|
20
|
+
- 共用逻辑被改动后,其他调用入口仍在使用旧写法,无人察觉。
|
|
21
|
+
- 出了问题想复盘,只能翻到大量对话记录,看不出当时按什么标准放行。
|
|
17
22
|
|
|
18
|
-
##
|
|
23
|
+
## 与工具自带工作流的区别
|
|
19
24
|
|
|
20
|
-
Claude Code 和 DeepSeek Harness
|
|
25
|
+
Claude Code 和 DeepSeek Harness 自带的工作流能力擅长**并行完成大量独立任务**。这个插件解决的是另一件事,两者可以叠加使用。
|
|
21
26
|
|
|
22
27
|
| | 工具自带的工作流 | 本插件 |
|
|
23
28
|
|---|---|---|
|
|
24
|
-
| 擅长 |
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
| 中断之后 |
|
|
29
|
+
| 擅长 | 并行完成大量独立任务 | 单个变更按阶段推进,逐层把关 |
|
|
30
|
+
| 如何放行 | 由脚本中的判断决定 | 由程序强制:检查未完成、问题未闭环时无法以「通过」提交 |
|
|
31
|
+
| 跨工具 | 各家私有 | 同一套流程跨工具通用 |
|
|
32
|
+
| 中断之后 | 进度依附于对话或单次运行 | 进度记录在仓库中,换会话、换工具均可继续 |
|
|
33
|
+
|
|
34
|
+
一句话:原生工作流负责把任务做完,这个插件负责判定产出是否达到生产级。
|
|
35
|
+
|
|
36
|
+
## 局限
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
使用前请先确认以下三条边界。
|
|
30
39
|
|
|
31
|
-
|
|
40
|
+
**存量项目需要一次收敛成本。** 判据按理想状态定义(方法不超过 50 行、类职责单一、核心域不依赖框架、构建产物需忽略、覆盖率需达标),且工具检查覆盖全量代码——存量违规同样按原级别上报并阻塞。存量项目首次运行会暴露大量存量问题,只能逐个修复,或逐个走豁免裁定;裁定通过的会沉淀进项目级清单,后续变更不再重复阻塞。
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
**Java 之外只有通用规范。** 23 份 skill 中 10 份技术栈无关、11 份为 Java 后端配套(含具体构建与检查命令、ArchUnit 规则、Spotless / PMD / SonarQube 配置)。非 Java 项目审查照常执行,但编译、格式、静态分析、覆盖率这几项确定性检查没有现成命令,需自行补充一份技术栈 skill,否则只能按降级申报跳过。
|
|
43
|
+
|
|
44
|
+
**架构规范只有 DDD 一套。** 包组织有两种等价的合法风格——四层分层(domain / application / infrastructure / interfaces)与六边形(内核 + 入站 / 出站适配器),采用哪种由项目声明,未声明时按四层认定。不按 DDD 组织的项目(例如 MVC + Service + DAO 直写)没有对应规范,会被判定为偏离规范并报出问题;项目已有自己的架构规范时以项目规范为准,但该规范需自行写入。
|
|
45
|
+
|
|
46
|
+
## 快速开始
|
|
47
|
+
|
|
48
|
+
项目还没用 OpenSpec 的话,先装上并初始化:
|
|
34
49
|
|
|
35
50
|
```bash
|
|
36
51
|
npm install -g @fission-ai/openspec
|
|
@@ -69,6 +84,8 @@ dsh plugin --profile web add @ifrankwang/openspec-agents
|
|
|
69
84
|
|
|
70
85
|
`web` 是 profile 名,换成你自己的(`~/.dsh/profiles/` 下的目录名)。装完重启该 profile;作用项目随当前会话所属项目自动确定,同一个 profile 下切换项目不必重装或改配置。编排工具需要在一个项目会话里使用。
|
|
71
86
|
|
|
87
|
+
要求 DSH 0.2.1 及以上。子代理的角色提示词与工具集由 Agent Preset 承载,profile 需包含 web 应用 bundle(`desktop` / `web` 模板都满足)。子代理委派为异步:分派后等完成通知,再继续下一步。
|
|
88
|
+
|
|
72
89
|
#### OpenCode
|
|
73
90
|
|
|
74
91
|
要求 OpenCode 2.x(1.x 的插件接口不兼容)。
|
|
@@ -91,22 +108,23 @@ npm install -D @ifrankwang/openspec-agents
|
|
|
91
108
|
|
|
92
109
|
首次加载时,插件会在项目 `.opencode/agents/` 下生成三个代理定义文件。这些文件带生成标记,插件**每次加载**都按当前版本重写;要自己改,先删掉文件里的生成标记。
|
|
93
110
|
|
|
94
|
-
###
|
|
111
|
+
### 第二步:确认安装成功
|
|
95
112
|
|
|
96
113
|
重启后查看可用 skill 列表:`openspec-standard`、`orchestrator` 是插件自带的两个入口,它们和一批规范类 skill 一起出现在列表里,就说明插件已经生效。这些 skill 可以像命令一样唤起(DSH 里输入 `/` 即可看到并调用)。
|
|
97
114
|
|
|
98
|
-
###
|
|
115
|
+
### 第三步:开始使用
|
|
99
116
|
|
|
100
117
|
- 项目里还没提变更时,调用 `/openspec-standard`,把提案标准装进项目。
|
|
101
|
-
-
|
|
118
|
+
- 提变更用 OpenSpec 官方命令:`/openspec-propose {具体需求描述}`,生成 `openspec/changes/<变更名>/` 下的文档;装过提案标准后,这些文档会按更严的标准产出。
|
|
119
|
+
- 然后调用 `/orchestrator {change-id}` 开始编排。`{change-id}` 就是那个目录名,`openspec list` 能看到当前有哪些变更。
|
|
102
120
|
|
|
103
121
|
> 不同工具里 skill 的呈现形式可能不同,以各工具实际显示为准。
|
|
104
122
|
|
|
105
|
-
###
|
|
123
|
+
### 默认无人值守
|
|
106
124
|
|
|
107
125
|
各安装方式**默认都开启无人值守**:流程里需要人拍板的环节(方案怎么选、无法自动验证的部分)由编排 AI 自行决断,不会停下来问你——**审查和门禁照常跑,变的只是「要不要来问你」**。希望它在这些地方先问你,让它关掉无人值守模式即可。
|
|
108
126
|
|
|
109
|
-
###
|
|
127
|
+
### 将以下两行加入 `.gitignore`
|
|
110
128
|
|
|
111
129
|
```
|
|
112
130
|
.worktree/
|
|
@@ -115,32 +133,6 @@ openspec/states/
|
|
|
115
133
|
|
|
116
134
|
两者都不该进版本库:前者是变更(以及独立审查)的独立工作副本,后者是流程进度。
|
|
117
135
|
|
|
118
|
-
## 它替你守住什么
|
|
119
|
-
|
|
120
|
-
- **分工协作**:不再是单个 AI 从头写到尾、写完自己说没问题。完整模式下,实现、检查、验收、质量审查是彼此分开的角色;精简模式把确定性检查交给开发者执行,再由一个审查者合并复核。
|
|
121
|
-
- **每一步都要过校验**:完整模式下,实现完先过工具检查,再走独立验收,最后过多个维度的质量审查,结论不通过就退回修复。流程规则由程序强制——检查没做完、问题没闭环,就不可能以「通过」提交;至于质量判断本身,仍由承担该环节的审查角色给出。
|
|
122
|
-
- **问题跟到底**:变更编排里,审查发现的问题会一直挂在变更上,要么修好,要么走豁免流程由报出该问题的审查者裁定通过,否则不允许继续推进。
|
|
123
|
-
- **写隔离**:变更在独立分支和独立工作副本里做,收口前你的基准分支上看不到这些改动。
|
|
124
|
-
- **中断能接上**:进度记在仓库里,问一句「现在跑到哪了」就能看到;关掉工具、隔一天、换个工具,都能从上次的位置继续。
|
|
125
|
-
- **规范驱动**:直接消费 OpenSpec 的提案、规格、设计、任务清单,流程与规范一一对应。
|
|
126
|
-
|
|
127
|
-
初始化编排时选流程模式,**默认是精简模式**:精简模式把确定性检查交给开发者执行、审查者分级复验,小改动不必走全套;完整模式开头多一道方案预检,验收也拆得更细。
|
|
128
|
-
|
|
129
|
-
## 内置的规范库
|
|
130
|
-
|
|
131
|
-
插件自带 23 份 skill(含两个流程入口),装了就有,不用自己写提示词;审查的 AI 会按当前任务需要自动挂上对应的一组。
|
|
132
|
-
|
|
133
|
-
**两个入口**
|
|
134
|
-
|
|
135
|
-
- `orchestrator` —— 编排准则,主代理按它分派子代理、推进流程。
|
|
136
|
-
- `openspec-standard` —— 提案标准初始化。
|
|
137
|
-
|
|
138
|
-
**10 份通用规范**:DDD 分层与六边形、REST 接口约定、数据库设计约定、安全审查基线、确定性质量门、测试设计、技术债识别、架构演进审查、API 自动化测试、隔离环境清理。
|
|
139
|
-
|
|
140
|
-
**11 份 Java 后端配套**:在通用规范之上补齐 Java / Spring 的实现细节 —— Lombok、日志与命名约定、REST 接口的 Java 实现、MyBatisPlus 与 Flyway、DDD 的 Java 实现与 ArchUnit 规则、Maven / Spotless / PMD / SonarQube 质量门、工具规则改进、Java 开发实践、Java 安全实现、Java 性能与可维护性、Java 技术债形态。
|
|
141
|
-
|
|
142
|
-
Java 后端项目会自动带上配套那套;其它语言目前只有通用规范生效,具体的构建与检查命令由你项目的规范补充。
|
|
143
|
-
|
|
144
136
|
## 提案标准化
|
|
145
137
|
|
|
146
138
|
OpenSpec 官方默认的流程是 `proposal → specs → design → tasks`,给出文档骨架与写作要求。这套标准在它前面加一道澄清与确认关,并把改动范围、契约变更、文档语言钉得更死。
|
|
@@ -157,10 +149,60 @@ OpenSpec 官方默认的流程是 `proposal → specs → design → tasks`,
|
|
|
157
149
|
| 任务划分 | 分组 + 每条带验证方式 | 追加:每组必须有真实代码改动(禁止「收尾组」凑数)、测试跟着实现同组、只覆盖本仓库 |
|
|
158
150
|
| 归档 | 官方归档命令本身会同步主规格 | 追加写作纪律:主规格只写终态,不留「原 X 改为 Y」;废弃项显式删除并补 REMOVED;同语义合并去重 |
|
|
159
151
|
|
|
160
|
-
|
|
152
|
+
**用法**:在目标项目的普通会话里调用 `/openspec-standard`(不要在被编排流程占用的会话里调用,那种会话不能改文件)。它会往项目写入 `openspec/config.yaml`(指定 `spec-driven-standard` schema)与 `openspec/schemas/spec-driven-standard/`。
|
|
153
|
+
|
|
154
|
+
装好之后提变更不必换命令,仍走 OpenSpec 官方用法:调用 `/openspec-propose {具体需求描述}`,生成的 proposal / specs / design / tasks 就会按这套标准产出,再由 `/orchestrator {change-id}` 接手实施与把关。
|
|
161
155
|
|
|
162
156
|
已经装过标准的项目重跑就是升级:标准更新到插件当前版本,你自己加的规则条目和项目背景会保留。
|
|
163
157
|
|
|
158
|
+
## 质量把关机制
|
|
159
|
+
|
|
160
|
+
「生产级」不是靠提示词要求的,而是逐层校验出来的。
|
|
161
|
+
|
|
162
|
+
**审查分工**
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
完整模式 方案预检 → 实现 → 工具检查 → 独立验收 → 五维质量审查 → 收口
|
|
166
|
+
精简模式 实现 → 合并审查 → 收口
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
最终判定不由实现者自己作出。完整模式下,检查、验收、质量审查由彼此独立的角色承担;精简模式由开发者执行确定性检查,再由一名审查者独立复核。
|
|
170
|
+
|
|
171
|
+
**工具与 AI 的分工**
|
|
172
|
+
|
|
173
|
+
确定性检查全部交给工具执行:编译、代码格式、架构约束、静态分析、单元测试与覆盖率、深度扫描,以及质量工具配置有没有被弱化。AI 审查只负责工具覆盖不到的部分——业务行为是否真实生效、关键数据是否真的落库、测试断言有没有放水、设计与规则边界是否站得住,并把其中可工具化的发现反过来沉淀成规则。判据锚在 skill 上,不是让模型现场发挥,所以非旗舰模型(flash 类)的产出下限同样有保障。
|
|
174
|
+
|
|
175
|
+
**通过标准**
|
|
176
|
+
|
|
177
|
+
- **五级定级**:Critical / High / Medium / Low / Info,各级判例锚定规范 skill;Info 仅为建议,Low 及以上未修复不得推进。
|
|
178
|
+
- **问题闭环**:审查报出的问题挂在变更上,修复后由报出该问题的审查者复核通过方为终结;确实无法修复的提交豁免申请,由同一审查者裁定,被驳回则退回重修。
|
|
179
|
+
- **程序强制**:检查未完成、问题未闭环时,无法以「通过」提交——由工具拦截,而非依赖提示词约束。
|
|
180
|
+
- **禁止规避**:压制注解、关闭规则、修改扫描平台状态一律视为豁免申请,须写明理由并接受裁定;同一问题修复未过两次,须先完成根因分析再修改。
|
|
181
|
+
- **规则沉淀**:可由工具检出的问题,要求同时提交业务问题与规则草案,将本次判定口径沉淀为后续的自动拦截规则。
|
|
182
|
+
|
|
183
|
+
**其他特性**
|
|
184
|
+
|
|
185
|
+
- **变更隔离**:变更在独立分支与独立工作副本中进行,收口前基准分支上看不到这些改动。
|
|
186
|
+
- **可续跑**:进度记录在仓库中,查询当前状态即可恢复;关闭工具、跨天、更换工具都能从上次位置继续。
|
|
187
|
+
- **规范驱动**:直接消费 OpenSpec 的提案、规格、设计与任务清单,流程与规范一一对应。
|
|
188
|
+
|
|
189
|
+
初始化编排时选择流程模式,**默认为精简模式**:精简模式下小改动不必走全套流程;完整模式在开头增加一道方案预检,验收也拆得更细。
|
|
190
|
+
|
|
191
|
+
## 内置的规范库
|
|
192
|
+
|
|
193
|
+
插件自带 23 份 skill(含两个流程入口),安装后即可使用,无需自行编写提示词;审查 AI 会按当前任务自动加载对应的一组,作为判据的**最低基准**——审查者还需结合自身领域知识扩展覆盖,不限于清单所列条目。
|
|
194
|
+
|
|
195
|
+
**两个入口**
|
|
196
|
+
|
|
197
|
+
- `orchestrator` —— 编排准则,主代理按它分派子代理、推进流程。
|
|
198
|
+
- `openspec-standard` —— 提案标准初始化。
|
|
199
|
+
|
|
200
|
+
**10 份通用规范**:DDD 分层与六边形、REST 接口约定、数据库设计约定、安全审查基线、确定性质量门、测试设计、技术债识别、架构演进审查、API 自动化测试、隔离环境清理。
|
|
201
|
+
|
|
202
|
+
**11 份 Java 后端配套**:在通用规范之上补齐 Java / Spring 的实现细节 —— Lombok、日志与命名约定、REST 接口的 Java 实现、MyBatisPlus 与 Flyway、DDD 的 Java 实现与 ArchUnit 规则、Maven / Spotless / PMD / SonarQube 质量门、工具规则改进、Java 开发实践、Java 安全实现、Java 性能与可维护性、Java 技术债形态。
|
|
203
|
+
|
|
204
|
+
Java 后端项目会自动带上配套那套;其它语言目前只有通用规范生效,具体的构建与检查命令由你项目的规范补充。
|
|
205
|
+
|
|
164
206
|
## 独立审查会话
|
|
165
207
|
|
|
166
208
|
除了绑定变更的编排,插件也支持不绑定任何变更的独立代码审查:对一个 PR 的改动区间、或整个代码库做一次完整审查。
|
|
@@ -173,7 +215,7 @@ OpenSpec 官方默认的流程是 `proposal → specs → design → tasks`,
|
|
|
173
215
|
|
|
174
216
|
审查在独立工作副本里进行。`fix` 模式全部通过后把工作分支合并回目标分支并清理环境;`none` 模式不合并,直接销毁工作环境并按严重级别、维度、文件位置归并给出审查结果。
|
|
175
217
|
|
|
176
|
-
##
|
|
218
|
+
## 对仓库的写入范围
|
|
177
219
|
|
|
178
220
|
编排流程只碰这几处,全在你的仓库内:
|
|
179
221
|
|
|
@@ -185,16 +227,7 @@ OpenSpec 官方默认的流程是 `proposal → specs → design → tasks`,
|
|
|
185
227
|
|
|
186
228
|
变更的全部任务组完成后,才会把这条分支合并回基准分支,随后自动删掉工作副本和分支。合并前如果基准分支已经往前走了,插件先比对两边改动:只有文档、规划文件或版本号这类无害变化时直接合并;有实质冲突时退回做一次收尾回归,通过后才合并。
|
|
187
229
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
## 适用场景与代价
|
|
191
|
-
|
|
192
|
-
| | |
|
|
193
|
-
|---|---|
|
|
194
|
-
| 适合 | 一个变更要真正落进代码库、有验收标准、要过几道检查、过程要能审计,可能跨天甚至跨工具的团队与项目 |
|
|
195
|
-
| 代价 | 比单个 AI 直接改更慢,Token 消耗更高 |
|
|
196
|
-
|
|
197
|
-
如果你愿意用一点成本换取稳定、可复现、可审计的实施过程,这个项目就是为你准备的。
|
|
230
|
+
**中途放弃**:删除工作副本与 `change/<变更号>` 分支即可,基准分支不会留下该变更的任何代码。
|
|
198
231
|
|
|
199
232
|
## 开发
|
|
200
233
|
|
|
@@ -42,7 +42,7 @@ capabilities: ["orchestrator"]
|
|
|
42
42
|
|
|
43
43
|
## 行为准则
|
|
44
44
|
|
|
45
|
-
-
|
|
45
|
+
- 每次子代理返回后,调用状态查询工具获取权威「下一步」指令并严格遵循;推进被拦时按工具给出的阻塞原因与修复建议处理,不自行推断其他动作。DSH 形态下「返回」指收到子代理完成通知,不是分派工具的即时返回(见「分派范式」的 DSH 异步委派)
|
|
46
46
|
- 分派/推进决策一律以工具返回为准:工具已给出明确分派指令时直接分派,不得先读状态文件交叉验证;仅门禁拒绝或工具给出修复建议时才读取状态文件核对
|
|
47
47
|
- 分派子代理前先查询状态确认当前阶段/层,确保不跳阶段或错层分派;工具视图呈现 worktree 未就绪时不得分派
|
|
48
48
|
- 不过度沟通:任务组内部不停下来向用户汇报,持续执行直到阻塞或完成
|
|
@@ -95,6 +95,14 @@ full 模式 verify_quality 以 5 个逻辑身份并行审查:同一物理审
|
|
|
95
95
|
|
|
96
96
|
不包含任何 task/issue 明细、文件清单、执行边界具体值等动态内容——一切交给状态查询工具。
|
|
97
97
|
|
|
98
|
+
### DSH 异步委派
|
|
99
|
+
|
|
100
|
+
DSH 0.2.1 起子代理委派为异步:`openspec_developer` / `openspec_reviewer` 调用后只返回子任务 id,子代理结束时会收到完成通知。
|
|
101
|
+
|
|
102
|
+
- 分派后不要立即查状态、不要重复分派同一角色;收到该子代理的完成通知后再查状态推进
|
|
103
|
+
- 并排分派的多个子代理各自完成、各自通知,全部到齐后再进入下一步
|
|
104
|
+
- 通知里的最终回答是子代理自述,权威状态仍以状态查询工具为准
|
|
105
|
+
|
|
98
106
|
## 无人值守行为约定
|
|
99
107
|
|
|
100
108
|
无人值守模式下,所有需拍板事项由你依据工具返回与已加载 skill 的规范自行裁决,**不向用户提问**:
|