@peterxiaoyang/superspec 0.1.0 → 0.1.2

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 CHANGED
@@ -1,47 +1,198 @@
1
1
  # SuperSpec
2
2
 
3
- SuperSpec 是基于 OpenSpec 的 agent 工作流叠加层。本仓库是独立的
4
- SuperSpec 项目,包根目录、工作流模板、Codex 适配文件、设计文档和
5
- CI 配置都放在仓库根目录。
3
+ [![npm version](https://img.shields.io/npm/v/@peterxiaoyang/superspec?style=flat-square)](https://www.npmjs.com/package/@peterxiaoyang/superspec)
4
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20.19.0-brightgreen?style=flat-square)](https://nodejs.org)
5
+ [![OpenSpec overlay](https://img.shields.io/badge/OpenSpec-overlay-6f42c1?style=flat-square)](https://github.com/Fission-AI/OpenSpec)
6
6
 
7
- 本包开发时使用 TypeScript;执行 `npm run build` 后生成 `dist/*.js`。
8
- 发布包里的命令行入口会运行编译后的 JavaScript。
7
+ > 面向 Codex OpenSpec 增强工作流约束层。
9
8
 
10
- 使用者需要 Node.js 20.19.0 或更高版本。仓库开发和测试当前使用
11
- Node.js 24,因为测试会直接执行 `.ts` 文件。
9
+ SuperSpec 是一个已经发布的 npm 包,适合已经认可 OpenSpec,但觉得默认流程在执行层还不够严格的团队。
12
10
 
13
- ## 安装
11
+ 它不替换 OpenSpec,也不改 OpenSpec 的工件关系。
12
+ 它做的是在 OpenSpec 现有变更生命周期之上,再加一层更强的执行纪律:
14
13
 
15
- GitHub 发布附件全局安装命令行工具:
14
+ - 先做现状调研,再谈设计是否站得住
15
+ - 先明确业务不变量,再做实现
16
+ - 先写清测试约束,再避免流于表面的测试
17
+ - 先经过多角色审查,再允许宣布“完成”
18
+ - 先确认归档保全完整,再真正归档
16
19
 
17
- ```text
20
+ 如果你只想用最原生、最轻量的 OpenSpec,而不想增加审查、测试、归档这些额外门禁,那你大概率不需要 SuperSpec。
21
+
22
+ SuperSpec 的核心定位是:
23
+
24
+ - **保留 OpenSpec 作为主流程**
25
+ - **把执行约束收敛成可复用的叠加层**
26
+ - **让阶段推进依赖证据,而不是模型一句“done”**
27
+
28
+ ## 为什么会有 SuperSpec
29
+
30
+ 很多团队在把 OpenSpec 和编码代理结合起来之后,真正出问题的地方往往不是“没有文档”,而是“有流程形状,但没有流程纪律”。
31
+
32
+ 常见问题是:
33
+
34
+ - 设计文档在,但不够细,指导不了后续实现
35
+ - 测试在,但没有证明真正重要的东西
36
+ - 任务打勾了,但缺少强校验
37
+ - 主线程自己写、自己审、自己宣布通过
38
+ - 归档做了,但辅助证据已经散了
39
+
40
+ SuperSpec 解决的不是“再造一套 OpenSpec”,而是把这些执行层的薄弱点补上。
41
+
42
+ ## SuperSpec 增加了什么
43
+
44
+ ### 1. 更严格的 propose 阶段
45
+
46
+ SuperSpec 保留 OpenSpec 的原生四件套工件:
47
+
48
+ - `proposal`
49
+ - `specs`
50
+ - `design`
51
+ - `tasks`
52
+
53
+ 同时补三类辅助产物:
54
+
55
+ - `discovery.md`:记录现状调研结果、已有行为、约束边界和需要先确认的问题
56
+ - `business-invariants.md`:整理这次变更必须守住的业务不变量,避免实现时把关键业务语义改丢
57
+ - `test-contract.md`:把测试覆盖范围、测试编号、约束映射和验证责任提前写清楚
58
+
59
+ 这样 `propose` 阶段就不只是“把文档写出来”,而是真正把设计、约束和测试责任提前收拢。
60
+
61
+ ### 2. 显式的多角色审查分工
62
+
63
+ SuperSpec 会安装这些项目内角色:
64
+
65
+ - `architect`
66
+ - `critic`
67
+ - `test-engineer`
68
+ - `code-reviewer`
69
+ - `verifier`
70
+
71
+ 这意味着审查在 SuperSpec 里是一个正式阶段,不是实现完成后的附带动作。
72
+
73
+ ### 3. 基于证据的门禁
74
+
75
+ SuperSpec 的 guard 会检查:
76
+
77
+ - 阶段进入条件
78
+ - 任务修改 / 任务完成 条件
79
+ - 审查完成 条件
80
+ - 可归档条件与归档保全条件
81
+
82
+ 目标很直接:**阶段推进必须依赖证据,而不是靠模型自述。**
83
+
84
+ ### 4. 更完整的辅助目录与归档保全
85
+
86
+ 每个变更下都会有 `.superspec/` 辅助运行目录,用于保存:
87
+
88
+ - 辅助产物:补足 OpenSpec 原生工件之外的调研、约束和测试文档
89
+ - 证据:保存 RED/GREEN、审查、验证、确认等过程证据
90
+ - 审查输出:保存各角色的审查结果和主线程的汇总结论
91
+ - 状态文件:保存当前 gate、指纹和阶段状态,便于恢复和重算
92
+ - ledger:保存关键事件记录,方便追溯流程推进过程
93
+ - 归档保全元数据:确保归档前后能核对辅助目录是否完整保留
94
+
95
+ 这让长任务恢复、阶段审计和归档后追溯更稳定。
96
+
97
+ ## 快速开始
98
+
99
+ ### 环境要求
100
+
101
+ - Node.js `>= 20.19.0`
102
+ - 本机 `PATH` 上可用兼容官方 `@fission-ai/openspec` 的 CLI,版本 `>= 1.4.1`,并支持 SuperSpec 依赖的 OpenSpec native surface
103
+ - 目标项目准备使用 OpenSpec + Codex 工作流入口
104
+
105
+ 执行 `superspec init` 时,无论选择 `project` 还是 `user` scope,如果未检测到 `openspec`、检测到 `openspec-chinese` 或其他不兼容变体,或版本低于 `1.4.1`,SuperSpec 都会自动尝试安装 / 升级官方 OpenSpec CLI;遇到全局 bin 被不兼容变体占用时会用覆盖模式重试。
106
+
107
+ Windows PowerShell 中如果遇到 npm 全局 bin 的 `.ps1` 执行策略报错,请显式使用 `.cmd` shim,例如 `superspec.cmd init --scope project`、`superspec.cmd guard check-init --change <change>`、`openspec.cmd status --change <change> --json`。
108
+
109
+ ### 安装
110
+
111
+ 优先走 npm:
112
+
113
+ ```bash
114
+ npm install -g @peterxiaoyang/superspec
115
+ ```
116
+
117
+
118
+ 如果你要固定到 GitHub release tarball,也可以:
119
+
120
+ ```bash
18
121
  npm install -g https://github.com/PeterYaoYang/SuperSpec/releases/download/v0.1.0/superspec-0.1.0.tgz
19
122
  ```
20
123
 
21
- 这个发布包由 `npm pack` 生成,里面包含已经编译好的 `dist/*.js`。
22
- 源码仓库本身不提交 `dist/`。
124
+ ### 初始化
23
125
 
24
- 安装完成后初始化 SuperSpec:
126
+ 在项目目录里执行:
25
127
 
26
- ```text
128
+ ```bash
129
+ superspec init --scope project
130
+ ```
131
+
132
+ 如果你就在项目目录里直接运行下面这条,通常也够了:
133
+
134
+ ```bash
27
135
  superspec init
28
136
  ```
29
137
 
30
- `init` 会询问安装范围:当前项目(`project`)或 Codex 用户目录
31
- (`user`)。直接回车默认选择 `project`;非交互运行时也默认选择
32
- `project`。脚本里建议显式传入范围:
138
+ 如果你要装到用户级目录,而不是当前项目:
33
139
 
34
- ```text
35
- superspec init --scope project
140
+ ```bash
36
141
  superspec init --scope user
37
142
  ```
38
143
 
39
- 以后发布到 npm 公共仓库后,安装命令会变成:
144
+ `superspec init` 会安装 SuperSpec 的工作流入口,并校验主路径需要的 OpenSpec Codex 入口。
145
+ 如果项目里还没有对应的 OpenSpec 初始化内容,且本机可用 `openspec` CLI,SuperSpec 会自动尝试执行:
146
+
147
+ - `openspec init --tools codex .`
148
+ - 必要时再执行 `openspec update --force .`
149
+
150
+ 也就是说,普通使用场景下,你不需要先手动跑一遍 `openspec init`,直接运行 `superspec init` 就可以。
151
+
152
+ ## 用户可见的工作流入口
153
+
154
+ SuperSpec 当前暴露的工作流入口是:
155
+
156
+ - `superspec-explore`
157
+ - `superspec-propose`
158
+ - `superspec-apply`
159
+ - `superspec-review`
160
+ - `superspec-archive`
161
+
162
+ ## 安装后会落什么东西
163
+
164
+ 项目级安装时,SuperSpec 会把项目内入口写到 `.codex/`,把变更级运行数据写到 `.superspec/`。
165
+
166
+ 主要辅助目录内容包括:
167
+
168
+ - `.superspec/artifacts/discovery.md`:记录现状调研、边界澄清和上游事实
169
+ - `.superspec/artifacts/business-invariants.md`:记录本次变更必须守住的业务不变量
170
+ - `.superspec/artifacts/test-contract.md`:记录测试覆盖矩阵、测试编号和约束映射
171
+ - `.superspec/evidence/...`:保存测试、审查、验证、用户确认等证据文件
172
+ - `.superspec/superspec-state.json`:保存 guard 重算后的状态摘要和指纹
173
+ - `.superspec/ledger.jsonl`:保存关键流程事件,便于审计和追溯
174
+
175
+ SuperSpec 支持:
176
+
177
+ - `project` scope
178
+ - `user` scope
179
+ - 基于清单的 `update`
180
+ - 基于清单的 `uninstall`
181
+
182
+ ## 设计原则
40
183
 
41
184
  ```text
42
- npm install -g @peterxiaoyang/superspec
185
+ 叠加,不是替代
186
+ → 证据,不是感觉
187
+ → 审查,不是自我批准
188
+ → 保全,不是归档后失忆
189
+ → 更严格,但不过度做重
43
190
  ```
44
191
 
45
- 不推荐普通使用者直接通过源码地址安装,例如
46
- `npm install -g github:PeterYaoYang/SuperSpec#main`。除非仓库提交了
47
- `dist/`,或者安装时的构建环境完全可控,否则应使用 GitHub 发布附件。
192
+ ## 致谢与灵感来源
193
+
194
+ SuperSpec 的思路不是凭空长出来的。它明显受这些项目影响:
195
+
196
+ - [OpenSpec](https://github.com/Fission-AI/OpenSpec)
197
+ - [oh-my-codex](https://github.com/Yeachan-Heo/oh-my-codex)
198
+ - [Superpowers](https://github.com/obra/superpowers)
package/dist/src/cli.js CHANGED
@@ -8,13 +8,13 @@ export function main(argv = process.argv.slice(2)) {
8
8
  return argparseExit;
9
9
  args = parse_argv(argv);
10
10
  const [decision] = dispatch(args);
11
- printDecision(decision);
11
+ printDecision(decision, { command: args.command });
12
12
  return decision.allowed ? 0 : 1;
13
13
  }
14
14
  catch (err) {
15
15
  const change = args?.change ?? "?";
16
16
  const errReason = err instanceof GuardError ? reason("guard_error", err.message) : reason("guard_internal_error", `${err.name}: ${err.message}`);
17
- printDecision(block(change, "guard_error", [errReason]));
17
+ printDecision(block(change, "guard_error", [errReason]), { command: args?.command });
18
18
  return 2;
19
19
  }
20
20
  }
@@ -1,3 +1,4 @@
1
+ import { command_zh } from "./i18n.js";
1
2
  const SIMPLE_COMMANDS = [
2
3
  "status",
3
4
  "recompute",
@@ -40,7 +41,11 @@ function rootUsage() {
40
41
  return `usage: superspec_guard [-h]\n {${COMMAND_LIST}}\n ...\n`;
41
42
  }
42
43
  function rootHelp() {
43
- return `${rootUsage()}\nsuperspec Sync Guard (v1)\n\npositional arguments:\n {${COMMAND_LIST}}\n\noptional arguments:\n -h, --help show this help message and exit\n`;
44
+ const commandLines = COMMANDS.map((command) => {
45
+ const zh = command_zh(command);
46
+ return ` ${command.padEnd(22, " ")} ${zh.label_zh} / ${zh.hint_zh}\n`;
47
+ }).join("");
48
+ return `${rootUsage()}\nSuperSpec 守护检查(v1)\n\n位置参数:\n {${COMMAND_LIST}}\n\n命令:\n${commandLines}\n可选参数:\n -h, --help 显示帮助并退出\n`;
44
49
  }
45
50
  function commandUsage(command) {
46
51
  const usageFlags = [
@@ -52,7 +57,8 @@ function commandUsage(command) {
52
57
  return `usage: superspec_guard ${command} ${usageFlags.join(" ")}\n`;
53
58
  }
54
59
  function commandHelp(command) {
55
- const lines = [commandUsage(command), "\noptional arguments:\n", " -h, --help show this help message and exit\n"];
60
+ const zh = command_zh(command);
61
+ const lines = [commandUsage(command), `\n${zh.label_zh}:${zh.hint_zh}\n`, "\n可选参数:\n", " -h, --help 显示帮助并退出\n"];
56
62
  for (const flag of requiredValueFlags(command)) {
57
63
  const metavariable = flag.slice(2).replace(/-/g, "_").toUpperCase();
58
64
  lines.push(` ${flag} ${metavariable}\n`);
@@ -73,7 +79,7 @@ function missingRequiredFlags(command, args) {
73
79
  }
74
80
  export function emitArgparsePreamble(argv) {
75
81
  if (argv.length === 0) {
76
- process.stderr.write(`${rootUsage()}superspec_guard: error: the following arguments are required: command\n`);
82
+ process.stderr.write(`${rootUsage()}superspec_guard:错误:缺少必填参数:command\n`);
77
83
  return 2;
78
84
  }
79
85
  const command = argv[0];
@@ -82,7 +88,7 @@ export function emitArgparsePreamble(argv) {
82
88
  return 0;
83
89
  }
84
90
  if (!COMMANDS.includes(command)) {
85
- process.stderr.write(`${rootUsage()}superspec_guard: error: argument command: invalid choice: '${command}' (choose from ${COMMAND_CHOICES})\n`);
91
+ process.stderr.write(`${rootUsage()}superspec_guard:错误:命令无效:'${command}';可选值:${COMMAND_CHOICES}\n`);
86
92
  return 2;
87
93
  }
88
94
  const args = argv.slice(1);
@@ -92,14 +98,14 @@ export function emitArgparsePreamble(argv) {
92
98
  }
93
99
  const missing = missingRequiredFlags(command, args);
94
100
  if (missing.length > 0) {
95
- process.stderr.write(`${commandUsage(command)}superspec_guard ${command}: error: the following arguments are required: ${missing.join(", ")}\n`);
101
+ process.stderr.write(`${commandUsage(command)}superspec_guard ${command}:错误:缺少必填参数:${missing.join(", ")}\n`);
96
102
  return 2;
97
103
  }
98
104
  return null;
99
105
  }
100
106
  export function parse_argv(argv) {
101
107
  if (argv.length === 0)
102
- throw new Error("missing command");
108
+ throw new Error("缺少命令");
103
109
  const command = argv[0];
104
110
  const args = argv.slice(1);
105
111
  const getValue = (flag) => {
@@ -110,33 +116,33 @@ export function parse_argv(argv) {
110
116
  };
111
117
  const change = getValue("--change");
112
118
  if (!change)
113
- throw new Error("missing required --change");
119
+ throw new Error("缺少必填参数 --change");
114
120
  if (command === "init") {
115
121
  if (!hasFlag(args, "--create"))
116
- throw new Error("missing required --create");
122
+ throw new Error("缺少必填参数 --create");
117
123
  return { command, change, create: true };
118
124
  }
119
125
  if (command === "check-artifact") {
120
126
  const artifact = getValue("--artifact");
121
127
  if (!artifact)
122
- throw new Error("missing required --artifact");
128
+ throw new Error("缺少必填参数 --artifact");
123
129
  return { command, change, artifact };
124
130
  }
125
131
  if (command === "check-enter") {
126
132
  const gate = getValue("--gate");
127
133
  if (!gate)
128
- throw new Error("missing required --gate");
134
+ throw new Error("缺少必填参数 --gate");
129
135
  return { command, change, gate };
130
136
  }
131
137
  if (command === "check-task-reopen" || command === "check-task-edit" || command === "check-task-complete") {
132
138
  const taskId = getValue("--task-id");
133
139
  if (!taskId)
134
- throw new Error("missing required --task-id");
140
+ throw new Error("缺少必填参数 --task-id");
135
141
  return { command, change, task_id: taskId };
136
142
  }
137
143
  const simple = new Set(SIMPLE_COMMANDS);
138
144
  if (!simple.has(command))
139
- throw new Error(`unknown command: ${command}`);
145
+ throw new Error(`未知命令:${command}`);
140
146
  return {
141
147
  command,
142
148
  change,
package/dist/src/gates.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
- import { ARTIFACT_ENTER_GATE, MAIN_ADJUDICATION_DECISIONS, REQUEST_CHANGES_ROUTES, NO_TDD_REASONS, OPENSPEC_ARTIFACTS, FINAL_VERIFICATION_ROLES, REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_SUPERSPEC_WORKFLOW_SKILLS, REQUIRED_OPENSPEC_CLI_SURFACES, REQUIRED_OPENSPEC_CODEX_SKILLS, REVIEW_GUIDANCE_ROLES, REVIEW_EVIDENCE_REQUIRED_FIELDS, TDD_MODES, VERIFY_EVIDENCE_REQUIRED_FIELDS, allow, block, commandExists, isObject, reason, renderList, repr, pinned_ref_key, safe_within, runCommand, sha256_text, runtime, toPosix, } from "./util.js";
4
- import { all_done, artifact_status_map, get_repo_root, is_done, normalize_gate } from "./openspec.js";
3
+ import { ARTIFACT_ENTER_GATE, MAIN_ADJUDICATION_DECISIONS, REQUEST_CHANGES_ROUTES, NO_TDD_REASONS, OPENSPEC_ARTIFACTS, FINAL_VERIFICATION_ROLES, REQUIRED_SUPERSPEC_AGENT_ROLES, REQUIRED_SUPERSPEC_WORKFLOW_SKILLS, REQUIRED_OPENSPEC_CODEX_SKILLS, REVIEW_GUIDANCE_ROLES, REVIEW_EVIDENCE_REQUIRED_FIELDS, TDD_MODES, VERIFY_EVIDENCE_REQUIRED_FIELDS, allow, block, isObject, reason, renderList, repr, pinned_ref_key, safe_within, sha256_text, runtime, toPosix, } from "./util.js";
4
+ import { all_done, artifact_status_map, get_repo_root, is_done, normalize_gate, openspec_cli_probe } from "./openspec.js";
5
5
  import { read_agent_toml_name, read_skill_frontmatter_name, sidecar_business_invariants_path, sidecar_discovery_path, sidecar_test_contract_path } from "./paths.js";
6
6
  import { business_invariant_ids, business_invariant_validation_reasons, automated_hard_business_invariant_ids, evidence_invariant_refs, evidence_invariant_ref_reasons, evidence_test_contract_invariant_reasons, human_confirmation_business_invariant_ids, invariant_matrix_coverage_reasons, post_implementation_business_invariant_ids, red_green_invariant_ids, test_contract_invariant_ids, } from "./invariants.js";
7
7
  import { evidence_test_id_reasons, declared_test_evidence_reasons, parse_spec_scenarios, parse_tasks, parse_test_contract_ids, parse_test_contract_records, red_green_test_ids, splitList, tasks_structure_hash, task_alternative_verification, task_test_evidence, task_test_refs, test_contract_covers_scenario, test_contract_invariant_refs_by_test, write_scope_conflict_reasons, } from "./tasks.js";
@@ -356,7 +356,7 @@ export function superspec_workflow_skill_reasons(repoRoot) {
356
356
  const skillsRoot = join(repoRoot, ".codex", "skills");
357
357
  const missing = REQUIRED_SUPERSPEC_WORKFLOW_SKILLS.filter((name) => !existsSync(join(skillsRoot, name, "SKILL.md")));
358
358
  if (missing.length > 0) {
359
- reasons.push(reason("superspec_init_missing", "SuperSpec workflow skills are missing; run superspec init --scope project to (re)install them", missing));
359
+ reasons.push(reason("superspec_init_missing", "SuperSpec workflow skills are missing; run `superspec init --scope project` to (re)install them", missing));
360
360
  }
361
361
  for (const name of REQUIRED_SUPERSPEC_WORKFLOW_SKILLS) {
362
362
  const skillPath = join(skillsRoot, name, "SKILL.md");
@@ -401,19 +401,14 @@ export function superspec_agent_reasons(repoRoot) {
401
401
  return reasons;
402
402
  }
403
403
  export function openspec_cli_capability_reasons() {
404
- if (!commandExists("openspec"))
405
- return [reason("openspec_cli_unavailable", "openspec CLI is not available in PATH")];
406
- const problems = [];
407
- for (const args of REQUIRED_OPENSPEC_CLI_SURFACES) {
408
- const proc = runCommand("openspec", [...args], { timeout: 15_000 });
409
- if (proc.error) {
410
- problems.push(reason("openspec_native_surface_missing", `\`openspec ${args.join(" ")}\` failed: ${proc.error.message}`));
411
- }
412
- else if (proc.status !== 0) {
413
- problems.push(reason("openspec_native_surface_missing", `\`openspec ${args.join(" ")}\` failed: ${(proc.stderr || proc.stdout).trim()}`));
414
- }
415
- }
416
- return problems;
404
+ const probe = openspec_cli_probe();
405
+ if (probe.ok)
406
+ return [];
407
+ if (probe.state === "missing")
408
+ return [reason("openspec_cli_unavailable", probe.message)];
409
+ if (probe.state === "too_old")
410
+ return [reason("openspec_cli_too_old", probe.message)];
411
+ return [reason("openspec_native_surface_missing", probe.message)];
417
412
  }
418
413
  export function evidence_schema_guard(change, changeRoot, repoRoot, evidences) {
419
414
  return [
@@ -0,0 +1,21 @@
1
+ export type ZhHint = {
2
+ label_zh: string;
3
+ hint_zh: string;
4
+ };
5
+ export type WorkflowTermHint = {
6
+ term: string;
7
+ label_zh: string;
8
+ hint_zh: string;
9
+ };
10
+ export declare function command_zh(command: string): ZhHint;
11
+ export declare function gate_zh(gate: string): ZhHint;
12
+ export declare function reason_zh(code: string): ZhHint;
13
+ export declare function decision_zh(decision: string): string;
14
+ export declare function translate_action_zh(action: string): string;
15
+ export declare function reason_message_zh(code: string, rawMessage: string, refs?: string[]): string;
16
+ export declare function trust_warning_zh(warning: string): string;
17
+ export declare function action_status_zh(status: string): string;
18
+ export declare function action_label_zh(action: string): string;
19
+ export declare function action_detail_zh(detail: string): string;
20
+ export declare function system_failure_zh(raw: string, fallback?: string): string;
21
+ export declare function workflow_terms_zh_for(command: string | undefined, _gate: string, reasonCodes: string[]): WorkflowTermHint[];