@peterxiaoyang/superspec 0.1.0 → 0.1.1
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/README.md +173 -24
- package/dist/src/cli.js +2 -2
- package/dist/src/cli_args.js +18 -12
- package/dist/src/i18n.d.ts +21 -0
- package/dist/src/i18n.js +639 -0
- package/dist/src/init_cli.d.ts +23 -0
- package/dist/src/init_cli.js +74 -14
- package/dist/src/project_init.d.ts +20 -0
- package/dist/src/project_init.js +48 -19
- package/dist/src/util.d.ts +20 -1
- package/dist/src/util.js +82 -3
- package/package.json +1 -1
- package/templates/workflow/prompts/architect.md +62 -55
- package/templates/workflow/prompts/code-reviewer.md +84 -77
- package/templates/workflow/prompts/critic.md +42 -35
- package/templates/workflow/prompts/test-engineer.md +73 -66
- package/templates/workflow/prompts/verifier.md +33 -26
- package/templates/workflow/skills/superspec-apply/SKILL.md +5 -3
- package/templates/workflow/skills/superspec-archive/SKILL.md +2 -0
- package/templates/workflow/skills/superspec-explore/SKILL.md +6 -4
- package/templates/workflow/skills/superspec-propose/SKILL.md +6 -4
- package/templates/workflow/skills/superspec-review/SKILL.md +2 -0
package/dist/src/init_cli.d.ts
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
1
|
+
import { runCommand } from "./core.ts";
|
|
2
|
+
import { type InstallScope } from "./install_engine.ts";
|
|
3
|
+
type InitArgs = {
|
|
4
|
+
path: string;
|
|
5
|
+
codexHome: string;
|
|
6
|
+
mode: "install" | "update" | "uninstall";
|
|
7
|
+
scope: InstallScope | null;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
force: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function maybe_install_missing_openspec(opts: {
|
|
12
|
+
cwd: string;
|
|
13
|
+
scope: InstallScope;
|
|
14
|
+
mode: InitArgs["mode"];
|
|
15
|
+
interactive?: boolean;
|
|
16
|
+
commandExistsFn?: (cmd: string, meta?: {
|
|
17
|
+
cwd?: string;
|
|
18
|
+
}) => boolean;
|
|
19
|
+
confirm?: (question: string) => Promise<boolean>;
|
|
20
|
+
run?: typeof runCommand;
|
|
21
|
+
writeStderr?: (text: string) => void;
|
|
22
|
+
}): Promise<"not-needed" | "installed" | "skipped" | "failed">;
|
|
1
23
|
export declare function main_init(argv?: string[]): number;
|
|
2
24
|
export declare function main_init_async(argv?: string[]): Promise<number>;
|
|
25
|
+
export {};
|
package/dist/src/init_cli.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { block, GuardError, printDecision, reason } from "./core.js";
|
|
2
|
-
import { project_init } from "./project_init.js";
|
|
1
|
+
import { block, commandExists, GuardError, printDecision, reason, runCommand } from "./core.js";
|
|
2
|
+
import { project_init, recommended_openspec_install_plan } from "./project_init.js";
|
|
3
3
|
import { install_workflow, uninstall_workflow, update_workflow } from "./install_engine.js";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { system_failure_zh } from "./i18n.js";
|
|
7
8
|
function usage() {
|
|
8
9
|
return "usage: superspec init [-h] [--scope {project,user}] [--path PATH] [--codex-home PATH] [--create] [--update] [--uninstall] [--dry-run] [--force]\n";
|
|
9
10
|
}
|
|
10
11
|
function help() {
|
|
11
|
-
return `${usage()}\
|
|
12
|
+
return `${usage()}\n可选参数:\n -h, --help 显示帮助并退出\n --scope {project,user} 安装到当前项目的 .codex 目录,或安装到用户级 Codex 目录(默认:project)\n --project 等价于 --scope project\n --user 等价于 --scope user\n --global 兼容别名,等价于 --user\n --path PATH --scope project 时使用的项目根目录(默认:当前目录)\n --codex-home PATH --scope user 时使用的 Codex 用户目录(默认:$CODEX_HOME 或 ~/.codex)\n --create 兼容参数;init 默认就会创建缺失内容\n --update 按 manifest 更新受管 SuperSpec 内容;用户改动文件保留,新的版本写入 *.new\n --uninstall 按 manifest 卸载受管 SuperSpec 内容;.superspec 数据与既有/用户改动文件会保留\n --dry-run 配合 --uninstall 时只预览将删除的文件,不实际修改\n --force 安装时覆盖已有且内容不同的文件,并保留 *.bak 备份\n`;
|
|
12
13
|
}
|
|
13
14
|
function parse_init_argv(argv) {
|
|
14
15
|
const getValue = (flag) => {
|
|
@@ -17,19 +18,19 @@ function parse_init_argv(argv) {
|
|
|
17
18
|
return undefined;
|
|
18
19
|
const value = argv[idx + 1];
|
|
19
20
|
if (value === undefined || value.startsWith("--"))
|
|
20
|
-
throw new GuardError(`${flag}
|
|
21
|
+
throw new GuardError(`${flag} 缺少取值`);
|
|
21
22
|
return value;
|
|
22
23
|
};
|
|
23
24
|
const update = argv.includes("--update");
|
|
24
25
|
const uninstall = argv.includes("--uninstall");
|
|
25
26
|
if (update && uninstall)
|
|
26
|
-
throw new GuardError("--update
|
|
27
|
+
throw new GuardError("--update 与 --uninstall 不能同时使用");
|
|
27
28
|
const scopeFlag = getValue("--scope");
|
|
28
29
|
if (scopeFlag !== undefined && !["project", "user", "global"].includes(scopeFlag))
|
|
29
|
-
throw new GuardError("--scope
|
|
30
|
+
throw new GuardError("--scope 只能是 project 或 user");
|
|
30
31
|
const userShortcut = argv.includes("--user") || argv.includes("--global");
|
|
31
32
|
if (userShortcut && argv.includes("--project"))
|
|
32
|
-
throw new GuardError("--user/--global
|
|
33
|
+
throw new GuardError("--user/--global 与 --project 不能同时使用");
|
|
33
34
|
let scope = scopeFlag === "global" ? "user" : scopeFlag ?? null;
|
|
34
35
|
if (userShortcut)
|
|
35
36
|
scope = "user";
|
|
@@ -47,6 +48,14 @@ function parse_init_argv(argv) {
|
|
|
47
48
|
function joinHomeCodex() {
|
|
48
49
|
return `${homedir()}/.codex`;
|
|
49
50
|
}
|
|
51
|
+
function commandFailure(proc) {
|
|
52
|
+
const output = (proc.error?.message ?? (proc.stderr || proc.stdout)).trim();
|
|
53
|
+
if (output)
|
|
54
|
+
return system_failure_zh(output, `安装命令执行失败(退出状态码 ${proc.status ?? "未知"})。`);
|
|
55
|
+
if (proc.status !== null)
|
|
56
|
+
return `安装命令执行失败(退出状态码 ${proc.status})。`;
|
|
57
|
+
return "安装命令执行失败,请查看终端日志后重试。";
|
|
58
|
+
}
|
|
50
59
|
function engineDecision(gate, projectRoot, result, nextActions) {
|
|
51
60
|
if (result.problems.length > 0) {
|
|
52
61
|
const decision = block("project", gate, result.problems.map((item) => reason(`${gate}_failed`, item)));
|
|
@@ -68,12 +77,12 @@ async function promptInstallScope() {
|
|
|
68
77
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
69
78
|
try {
|
|
70
79
|
for (;;) {
|
|
71
|
-
const answer = (await rl.question("
|
|
72
|
-
if (answer === "" || answer === "project" || answer === "p" || answer === "1")
|
|
80
|
+
const answer = (await rl.question("请选择安装范围:project 还是 user?[project] ")).trim().toLowerCase();
|
|
81
|
+
if (answer === "" || answer === "project" || answer === "p" || answer === "1" || answer === "项目")
|
|
73
82
|
return "project";
|
|
74
|
-
if (answer === "user" || answer === "u" || answer === "2" || answer === "global" || answer === "g")
|
|
83
|
+
if (answer === "user" || answer === "u" || answer === "2" || answer === "global" || answer === "g" || answer === "用户")
|
|
75
84
|
return "user";
|
|
76
|
-
process.stderr.write("
|
|
85
|
+
process.stderr.write("请输入 project 或 user。\n");
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
88
|
finally {
|
|
@@ -83,6 +92,56 @@ async function promptInstallScope() {
|
|
|
83
92
|
function canPrompt() {
|
|
84
93
|
return Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
85
94
|
}
|
|
95
|
+
async function promptYesNo(question, defaultYes = true) {
|
|
96
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
97
|
+
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
98
|
+
try {
|
|
99
|
+
for (;;) {
|
|
100
|
+
const answer = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
101
|
+
if (answer === "")
|
|
102
|
+
return defaultYes;
|
|
103
|
+
if (["y", "yes", "是", "好", "确认"].includes(answer))
|
|
104
|
+
return true;
|
|
105
|
+
if (["n", "no", "否", "不", "取消"].includes(answer))
|
|
106
|
+
return false;
|
|
107
|
+
process.stderr.write("请输入 y / n,或输入 是 / 否。\n");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
rl.close();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export async function maybe_install_missing_openspec(opts) {
|
|
115
|
+
const commandExistsFn = opts.commandExistsFn ?? ((cmd, meta) => commandExists(cmd, { cwd: meta?.cwd }));
|
|
116
|
+
if (opts.scope !== "project" || opts.mode !== "install")
|
|
117
|
+
return "not-needed";
|
|
118
|
+
if (commandExistsFn("openspec", { cwd: opts.cwd }))
|
|
119
|
+
return "not-needed";
|
|
120
|
+
if (!(opts.interactive ?? canPrompt()))
|
|
121
|
+
return "skipped";
|
|
122
|
+
const plan = recommended_openspec_install_plan({ cwd: opts.cwd, commandExistsFn });
|
|
123
|
+
if (plan === null)
|
|
124
|
+
return "skipped";
|
|
125
|
+
const confirm = opts.confirm ?? ((question) => promptYesNo(question));
|
|
126
|
+
const accepted = await confirm(`未检测到 openspec CLI。是否现在尝试自动安装?\n将执行:${plan.rendered}`);
|
|
127
|
+
if (!accepted)
|
|
128
|
+
return "skipped";
|
|
129
|
+
const writeStderr = opts.writeStderr ?? ((text) => {
|
|
130
|
+
process.stderr.write(text);
|
|
131
|
+
});
|
|
132
|
+
writeStderr(`正在安装 OpenSpec CLI:${plan.rendered}\n`);
|
|
133
|
+
const proc = (opts.run ?? runCommand)(plan.cmd, plan.args, { cwd: opts.cwd, timeout: 300_000 });
|
|
134
|
+
if (proc.error || proc.status !== 0) {
|
|
135
|
+
writeStderr(`自动安装 OpenSpec CLI 失败:${commandFailure(proc)}\n`);
|
|
136
|
+
return "failed";
|
|
137
|
+
}
|
|
138
|
+
if (!commandExistsFn("openspec", { cwd: opts.cwd })) {
|
|
139
|
+
writeStderr("安装命令已完成,但当前 PATH 里仍未检测到 `openspec`。请重新打开终端或确认全局 bin 已在 PATH 中,然后重新运行 superspec init。\n");
|
|
140
|
+
return "failed";
|
|
141
|
+
}
|
|
142
|
+
writeStderr("OpenSpec CLI 安装完成,继续执行 superspec init。\n");
|
|
143
|
+
return "installed";
|
|
144
|
+
}
|
|
86
145
|
function run_init(args, scope) {
|
|
87
146
|
const targetRoot = scope === "user" ? args.codexHome : args.path;
|
|
88
147
|
const gatePrefix = scope === "user" ? "user" : "project";
|
|
@@ -107,7 +166,7 @@ function run_init(args, scope) {
|
|
|
107
166
|
}
|
|
108
167
|
summary.install_scope = scope;
|
|
109
168
|
summary.install_root = targetRoot;
|
|
110
|
-
printDecision(summary);
|
|
169
|
+
printDecision(summary, { command: "init" });
|
|
111
170
|
return summary.allowed ? 0 : 1;
|
|
112
171
|
}
|
|
113
172
|
export function main_init(argv = process.argv.slice(2)) {
|
|
@@ -122,7 +181,7 @@ export function main_init(argv = process.argv.slice(2)) {
|
|
|
122
181
|
catch (err) {
|
|
123
182
|
const change = "project";
|
|
124
183
|
const errReason = err instanceof GuardError ? reason("guard_error", err.message) : reason("guard_internal_error", `${err.name}: ${err.message}`);
|
|
125
|
-
printDecision(block(change, "guard_error", [errReason]));
|
|
184
|
+
printDecision(block(change, "guard_error", [errReason]), { command: "init" });
|
|
126
185
|
return 2;
|
|
127
186
|
}
|
|
128
187
|
}
|
|
@@ -134,12 +193,13 @@ export async function main_init_async(argv = process.argv.slice(2)) {
|
|
|
134
193
|
}
|
|
135
194
|
const args = parse_init_argv(argv);
|
|
136
195
|
const scope = args.scope ?? (canPrompt() ? await promptInstallScope() : "project");
|
|
196
|
+
await maybe_install_missing_openspec({ cwd: args.path, scope, mode: args.mode });
|
|
137
197
|
return run_init(args, scope);
|
|
138
198
|
}
|
|
139
199
|
catch (err) {
|
|
140
200
|
const change = "project";
|
|
141
201
|
const errReason = err instanceof GuardError ? reason("guard_error", err.message) : reason("guard_internal_error", `${err.name}: ${err.message}`);
|
|
142
|
-
printDecision(block(change, "guard_error", [errReason]));
|
|
202
|
+
printDecision(block(change, "guard_error", [errReason]), { command: "init" });
|
|
143
203
|
return 2;
|
|
144
204
|
}
|
|
145
205
|
}
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { type JsonMap } from "./core.ts";
|
|
2
|
+
export declare const OPENSPEC_NPM_PACKAGE = "@fission-ai/openspec";
|
|
3
|
+
export declare const OPENSPEC_INSTALL_DOC_URL = "https://github.com/Fission-AI/OpenSpec#readme";
|
|
4
|
+
export type OpenspecInstallPlan = {
|
|
5
|
+
manager: "npm" | "pnpm" | "yarn" | "bun";
|
|
6
|
+
cmd: string;
|
|
7
|
+
args: string[];
|
|
8
|
+
rendered: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function recommended_openspec_install_plan(opts?: {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
commandExistsFn?: (cmd: string, meta?: {
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}) => boolean;
|
|
15
|
+
}): OpenspecInstallPlan | null;
|
|
16
|
+
export declare function missing_openspec_cli_message(opts?: {
|
|
17
|
+
cwd?: string;
|
|
18
|
+
commandExistsFn?: (cmd: string, meta?: {
|
|
19
|
+
cwd?: string;
|
|
20
|
+
}) => boolean;
|
|
21
|
+
}): string;
|
|
2
22
|
export declare function project_init(repoRootRaw?: string, opts?: {
|
|
3
23
|
force?: boolean;
|
|
4
24
|
}): JsonMap;
|
package/dist/src/project_init.js
CHANGED
|
@@ -2,15 +2,44 @@ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "no
|
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_OPENSPEC_CODEX_SKILLS, block, reason, read_agent_toml_name, read_skill_frontmatter_name, commandExists, runCommand, } from "./core.js";
|
|
4
4
|
import { install_workflow } from "./install_engine.js";
|
|
5
|
+
import { system_failure_zh } from "./i18n.js";
|
|
6
|
+
export const OPENSPEC_NPM_PACKAGE = "@fission-ai/openspec";
|
|
7
|
+
export const OPENSPEC_INSTALL_DOC_URL = "https://github.com/Fission-AI/OpenSpec#readme";
|
|
5
8
|
const ROLE_DESCRIPTIONS = {
|
|
6
|
-
architect: "
|
|
7
|
-
critic: "
|
|
8
|
-
"test-engineer": "
|
|
9
|
-
"code-reviewer": "
|
|
10
|
-
verifier: "
|
|
9
|
+
architect: "系统设计、边界、接口与长期取舍",
|
|
10
|
+
critic: "对计划、证据、假设与范围漂移做对抗审查",
|
|
11
|
+
"test-engineer": "测试策略、覆盖率与 RED/GREEN 证据审查",
|
|
12
|
+
"code-reviewer": "代码 / 规格 / 安全审查",
|
|
13
|
+
verifier: "最终完成证据与验证审查",
|
|
11
14
|
};
|
|
12
15
|
function commandFailure(proc) {
|
|
13
|
-
return (proc.error?.message ?? (proc.stderr || proc.stdout)).trim();
|
|
16
|
+
return system_failure_zh((proc.error?.message ?? (proc.stderr || proc.stdout)).trim(), proc.status !== null && proc.status !== undefined ? `命令执行失败(退出状态码 ${proc.status})。` : "命令执行失败,请查看终端日志后重试。");
|
|
17
|
+
}
|
|
18
|
+
function renderCommand(cmd, args) {
|
|
19
|
+
return [cmd, ...args].join(" ");
|
|
20
|
+
}
|
|
21
|
+
export function recommended_openspec_install_plan(opts = {}) {
|
|
22
|
+
const commandExistsFn = opts.commandExistsFn ?? ((cmd, meta) => commandExists(cmd, { cwd: meta?.cwd }));
|
|
23
|
+
const versionedPackage = `${OPENSPEC_NPM_PACKAGE}@latest`;
|
|
24
|
+
const candidates = [
|
|
25
|
+
{ manager: "npm", cmd: "npm", args: ["install", "-g", versionedPackage] },
|
|
26
|
+
{ manager: "pnpm", cmd: "pnpm", args: ["add", "-g", versionedPackage] },
|
|
27
|
+
{ manager: "yarn", cmd: "yarn", args: ["global", "add", versionedPackage] },
|
|
28
|
+
{ manager: "bun", cmd: "bun", args: ["add", "-g", versionedPackage] },
|
|
29
|
+
];
|
|
30
|
+
for (const candidate of candidates) {
|
|
31
|
+
if (!commandExistsFn(candidate.cmd, { cwd: opts.cwd }))
|
|
32
|
+
continue;
|
|
33
|
+
return { ...candidate, rendered: renderCommand(candidate.cmd, candidate.args) };
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
export function missing_openspec_cli_message(opts = {}) {
|
|
38
|
+
const plan = recommended_openspec_install_plan(opts);
|
|
39
|
+
if (plan) {
|
|
40
|
+
return `PATH 中缺少 openspec CLI。可先运行 \`${plan.rendered}\` 安装,然后重新运行 superspec init --scope project。`;
|
|
41
|
+
}
|
|
42
|
+
return `PATH 中缺少 openspec CLI。请先安装 OpenSpec CLI(${OPENSPEC_INSTALL_DOC_URL}),然后重新运行 superspec init --scope project。`;
|
|
14
43
|
}
|
|
15
44
|
function openspecSkillProblems(repoRoot) {
|
|
16
45
|
const skillsRoot = join(repoRoot, ".codex", "skills");
|
|
@@ -34,9 +63,9 @@ function writeSuperSpecAgent(repoRoot, name) {
|
|
|
34
63
|
`description = "${ROLE_DESCRIPTIONS[name] ?? `superspec ${name} role`}"`,
|
|
35
64
|
'model_reasoning_effort = "high"',
|
|
36
65
|
'developer_instructions = """',
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
66
|
+
`你是仓库本地的 superspec ${name} native subagent。`,
|
|
67
|
+
"遵循分配给你的 superspec gate 证据任务,引用具体文件,并把阻塞点上报主线程。",
|
|
68
|
+
"不要用主线程自审替代必须的角色证据。",
|
|
40
69
|
'"""',
|
|
41
70
|
"",
|
|
42
71
|
].join("\n"), "utf8");
|
|
@@ -51,16 +80,16 @@ function writeSuperSpecPrompt(repoRoot, name) {
|
|
|
51
80
|
'argument-hint: "superspec gate evidence task"',
|
|
52
81
|
"---",
|
|
53
82
|
"",
|
|
54
|
-
|
|
83
|
+
`你是仓库本地的 superspec ${name} 角色。`,
|
|
55
84
|
"",
|
|
56
|
-
"
|
|
85
|
+
"请基于具体文件证据审查提供的 superspec gate 上下文,输出简洁的通过 / 阻塞报告,引用 source anchors 与 target refs,并且不要用主线程自审替代必须的 native-subagent 证据。",
|
|
57
86
|
"",
|
|
58
87
|
].join("\n"), "utf8");
|
|
59
88
|
return filePath;
|
|
60
89
|
}
|
|
61
90
|
function ensureOpenSpecCodex(repoRoot, actions) {
|
|
62
91
|
if (!commandExists("openspec", { cwd: repoRoot }))
|
|
63
|
-
return [
|
|
92
|
+
return [missing_openspec_cli_message({ cwd: repoRoot })];
|
|
64
93
|
let problems = openspecSkillProblems(repoRoot);
|
|
65
94
|
if (problems.length === 0) {
|
|
66
95
|
actions.push({ action: "openspec_codex_skills", status: "ok" });
|
|
@@ -71,10 +100,10 @@ function ensureOpenSpecCodex(repoRoot, actions) {
|
|
|
71
100
|
action: "openspec init --tools codex .",
|
|
72
101
|
status: init.status === 0 ? "updated" : "failed",
|
|
73
102
|
refs: problems,
|
|
74
|
-
detail: init.status === 0 ? undefined : (init
|
|
103
|
+
detail: init.status === 0 ? undefined : commandFailure(init),
|
|
75
104
|
});
|
|
76
105
|
if (init.error || init.status !== 0)
|
|
77
|
-
return [`openspec init
|
|
106
|
+
return [`openspec init 执行失败:${commandFailure(init)}`];
|
|
78
107
|
problems = openspecSkillProblems(repoRoot);
|
|
79
108
|
if (problems.length === 0)
|
|
80
109
|
return [];
|
|
@@ -83,12 +112,12 @@ function ensureOpenSpecCodex(repoRoot, actions) {
|
|
|
83
112
|
action: "openspec update --force .",
|
|
84
113
|
status: update.status === 0 ? "updated" : "failed",
|
|
85
114
|
refs: problems,
|
|
86
|
-
detail: update.status === 0 ? undefined : (update
|
|
115
|
+
detail: update.status === 0 ? undefined : commandFailure(update),
|
|
87
116
|
});
|
|
88
117
|
if (update.error || update.status !== 0)
|
|
89
|
-
return [`openspec update
|
|
118
|
+
return [`openspec update 执行失败:${commandFailure(update)}`];
|
|
90
119
|
problems = openspecSkillProblems(repoRoot);
|
|
91
|
-
return problems.length === 0 ? [] : [`OpenSpec
|
|
120
|
+
return problems.length === 0 ? [] : [`OpenSpec 配套技能文件仍然缺失或无效:${problems.join(", ")}`];
|
|
92
121
|
}
|
|
93
122
|
function ensureSuperSpecRoles(repoRoot, actions) {
|
|
94
123
|
const problems = [];
|
|
@@ -99,14 +128,14 @@ function ensureSuperSpecRoles(repoRoot, actions) {
|
|
|
99
128
|
created.push(writeSuperSpecAgent(repoRoot, name));
|
|
100
129
|
}
|
|
101
130
|
else if (read_agent_toml_name(agentPath) !== name) {
|
|
102
|
-
problems.push(`
|
|
131
|
+
problems.push(`agent 文件无效:${agentPath}`);
|
|
103
132
|
}
|
|
104
133
|
const promptPath = join(repoRoot, ".codex", "prompts", `${name}.md`);
|
|
105
134
|
if (!existsSync(promptPath) || !statSync(promptPath).isFile()) {
|
|
106
135
|
created.push(writeSuperSpecPrompt(repoRoot, name));
|
|
107
136
|
}
|
|
108
137
|
else if (!readFileSync(promptPath, "utf8").trim()) {
|
|
109
|
-
problems.push(`
|
|
138
|
+
problems.push(`prompt 文件为空:${promptPath}`);
|
|
110
139
|
}
|
|
111
140
|
}
|
|
112
141
|
actions.push({
|
package/dist/src/util.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WorkflowTermHint } from "./i18n.ts";
|
|
1
2
|
export declare const SCHEMA_VERSION = 1;
|
|
2
3
|
export declare const GUARD_VERSION = "superspec-guard@1";
|
|
3
4
|
export declare const CONFIG_FILENAME = "config.yaml";
|
|
@@ -12,6 +13,9 @@ export type Reason = {
|
|
|
12
13
|
code: string;
|
|
13
14
|
message: string;
|
|
14
15
|
refs: string[];
|
|
16
|
+
label_zh?: string;
|
|
17
|
+
hint_zh?: string;
|
|
18
|
+
message_zh?: string;
|
|
15
19
|
};
|
|
16
20
|
export type Decision = {
|
|
17
21
|
allowed: boolean;
|
|
@@ -24,6 +28,16 @@ export type Decision = {
|
|
|
24
28
|
block_reasons: Reason[];
|
|
25
29
|
next_allowed_actions: string[];
|
|
26
30
|
trust_warnings: string[];
|
|
31
|
+
actions?: JsonMap[];
|
|
32
|
+
decision_zh?: string;
|
|
33
|
+
gate_label_zh?: string;
|
|
34
|
+
gate_hint_zh?: string;
|
|
35
|
+
command?: string;
|
|
36
|
+
command_label_zh?: string;
|
|
37
|
+
command_hint_zh?: string;
|
|
38
|
+
next_allowed_actions_zh?: string[];
|
|
39
|
+
trust_warnings_zh?: string[];
|
|
40
|
+
workflow_terms_zh?: WorkflowTermHint[];
|
|
27
41
|
};
|
|
28
42
|
export type TaskInfo = {
|
|
29
43
|
task_id: string;
|
|
@@ -82,7 +96,12 @@ export declare function block(change: string, gate: string, reasons: Reason[], o
|
|
|
82
96
|
gate_summary?: Record<string, any>;
|
|
83
97
|
next_actions?: string[];
|
|
84
98
|
}): Decision;
|
|
85
|
-
export declare function
|
|
99
|
+
export declare function decorateDecision(decision: JsonMap, opts?: {
|
|
100
|
+
command?: string;
|
|
101
|
+
}): JsonMap;
|
|
102
|
+
export declare function printDecision(decision: JsonMap, opts?: {
|
|
103
|
+
command?: string;
|
|
104
|
+
}): void;
|
|
86
105
|
export declare function runCommand(cmd: string, args: string[], opts?: {
|
|
87
106
|
cwd?: string;
|
|
88
107
|
timeout?: number;
|
package/dist/src/util.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { action_detail_zh, action_label_zh, action_status_zh, command_zh, decision_zh, gate_zh, reason_message_zh, reason_zh, translate_action_zh, trust_warning_zh, workflow_terms_zh_for } from "./i18n.js";
|
|
5
6
|
export const SCHEMA_VERSION = 1;
|
|
6
7
|
export const GUARD_VERSION = "superspec-guard@1";
|
|
7
8
|
export const CONFIG_FILENAME = "config.yaml";
|
|
@@ -234,7 +235,8 @@ export class GuardError extends Error {
|
|
|
234
235
|
}
|
|
235
236
|
export const runtime = {};
|
|
236
237
|
export function reason(code, message, refs = null) {
|
|
237
|
-
|
|
238
|
+
const zh = reason_zh(code);
|
|
239
|
+
return { code, message, refs: refs ?? [], label_zh: zh.label_zh, hint_zh: zh.hint_zh };
|
|
238
240
|
}
|
|
239
241
|
export function pinned_ref_key(item) {
|
|
240
242
|
return `${String(item.path)}\u0000${String(item.blob_sha)}`;
|
|
@@ -273,8 +275,85 @@ export function block(change, gate, reasons, opts = {}) {
|
|
|
273
275
|
trust_warnings: trustWarnings(),
|
|
274
276
|
};
|
|
275
277
|
}
|
|
276
|
-
export function
|
|
277
|
-
|
|
278
|
+
export function decorateDecision(decision, opts = {}) {
|
|
279
|
+
const gateInfo = gate_zh(String(decision.gate ?? ""));
|
|
280
|
+
const command = opts.command ?? (typeof decision.command === "string" ? String(decision.command) : "");
|
|
281
|
+
const commandInfo = command ? command_zh(command) : null;
|
|
282
|
+
const reasons = Array.isArray(decision.block_reasons)
|
|
283
|
+
? decision.block_reasons.map((item) => {
|
|
284
|
+
const base = item && typeof item === "object" ? { ...item } : { code: String(item), message: "", refs: [] };
|
|
285
|
+
const zh = reason_zh(String(base.code ?? ""));
|
|
286
|
+
return { ...base, label_zh: zh.label_zh, hint_zh: zh.hint_zh };
|
|
287
|
+
})
|
|
288
|
+
: [];
|
|
289
|
+
const nextActions = Array.isArray(decision.next_allowed_actions) ? decision.next_allowed_actions.map((item) => String(item)) : [];
|
|
290
|
+
return {
|
|
291
|
+
...decision,
|
|
292
|
+
command: command || decision.command,
|
|
293
|
+
decision_zh: decision_zh(String(decision.decision ?? "")),
|
|
294
|
+
gate_label_zh: gateInfo.label_zh,
|
|
295
|
+
gate_hint_zh: gateInfo.hint_zh,
|
|
296
|
+
command_label_zh: commandInfo?.label_zh,
|
|
297
|
+
command_hint_zh: commandInfo?.hint_zh,
|
|
298
|
+
block_reasons: reasons,
|
|
299
|
+
next_allowed_actions_zh: nextActions.map((item) => translate_action_zh(item)),
|
|
300
|
+
trust_warnings_zh: Array.isArray(decision.trust_warnings) ? decision.trust_warnings.map((item) => trust_warning_zh(String(item))) : [],
|
|
301
|
+
workflow_terms_zh: workflow_terms_zh_for(command || undefined, String(decision.gate ?? ""), reasons.map((item) => String(item.code ?? ""))),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function sanitizeReasonForOutput(item) {
|
|
305
|
+
const refs = Array.isArray(item.refs) ? item.refs.map((ref) => String(ref)) : [];
|
|
306
|
+
return {
|
|
307
|
+
...item,
|
|
308
|
+
refs,
|
|
309
|
+
message: String(item.message ?? ""),
|
|
310
|
+
message_zh: reason_message_zh(String(item.code ?? ""), String(item.message ?? ""), refs),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function sanitizeDecisionForOutput(decision) {
|
|
314
|
+
const reasons = Array.isArray(decision.block_reasons) ? decision.block_reasons.map((item) => sanitizeReasonForOutput(item)) : [];
|
|
315
|
+
const nextActions = Array.isArray(decision.next_allowed_actions)
|
|
316
|
+
? decision.next_allowed_actions.map((item) => String(item))
|
|
317
|
+
: [];
|
|
318
|
+
const nextActionsZh = Array.isArray(decision.next_allowed_actions_zh)
|
|
319
|
+
? decision.next_allowed_actions_zh.map((item) => String(item))
|
|
320
|
+
: [];
|
|
321
|
+
const trustWarnings = Array.isArray(decision.trust_warnings)
|
|
322
|
+
? decision.trust_warnings.map((item) => String(item))
|
|
323
|
+
: [];
|
|
324
|
+
const trustWarningsZh = Array.isArray(decision.trust_warnings_zh)
|
|
325
|
+
? decision.trust_warnings_zh.map((item) => String(item))
|
|
326
|
+
: [];
|
|
327
|
+
const actions = Array.isArray(decision.actions)
|
|
328
|
+
? decision.actions.map((item) => {
|
|
329
|
+
const base = item && typeof item === "object" ? { ...item } : { action: String(item) };
|
|
330
|
+
const action = String(base.action ?? "");
|
|
331
|
+
const status = typeof base.status === "string" ? String(base.status) : "";
|
|
332
|
+
const detail = typeof base.detail === "string" ? String(base.detail) : "";
|
|
333
|
+
return {
|
|
334
|
+
...base,
|
|
335
|
+
action,
|
|
336
|
+
status: status || base.status,
|
|
337
|
+
detail: detail || base.detail,
|
|
338
|
+
action_zh: action_label_zh(action),
|
|
339
|
+
status_zh: status ? action_status_zh(status) : undefined,
|
|
340
|
+
detail_zh: detail ? action_detail_zh(detail) : undefined,
|
|
341
|
+
};
|
|
342
|
+
})
|
|
343
|
+
: decision.actions;
|
|
344
|
+
return {
|
|
345
|
+
...decision,
|
|
346
|
+
block_reasons: reasons,
|
|
347
|
+
next_allowed_actions: nextActions,
|
|
348
|
+
next_allowed_actions_zh: nextActionsZh,
|
|
349
|
+
trust_warnings: trustWarnings,
|
|
350
|
+
trust_warnings_zh: trustWarningsZh,
|
|
351
|
+
actions,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
export function printDecision(decision, opts = {}) {
|
|
355
|
+
const decorated = decorateDecision(decision, opts);
|
|
356
|
+
process.stdout.write(`${JSON.stringify(sanitizeDecisionForOutput(decorated), null, 2)}\n`);
|
|
278
357
|
}
|
|
279
358
|
export function runCommand(cmd, args, opts = {}) {
|
|
280
359
|
const platform = opts.platform ?? process.platform;
|