@peterxiaoyang/superspec 0.1.9 → 0.1.10

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.
@@ -4,12 +4,36 @@ import { install_workflow, uninstall_workflow, update_workflow } from "./install
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
+ import { action_detail_zh, action_status_zh, reason_message_zh, system_failure_zh } from "./i18n.js";
8
8
  function usage() {
9
9
  return "usage: superspec init [-h] [--scope {project,user}] [--path PATH] [--codex-home PATH] [--format {json,agent,user}] [--create] [--update] [--uninstall] [--dry-run] [--force]\n";
10
10
  }
11
11
  function help() {
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 --format {json,agent,user} 输出格式;json 用于诊断,agent/user 用于安全展示\n --user-facing 等价于 --format user\n --create 兼容参数;init 默认就会创建缺失内容\n --update 按 manifest 更新 SuperSpec 管理的文件;用户改动文件保留,新的版本写入 *.new\n --uninstall 按 manifest 卸载 SuperSpec 管理的文件;.superspec 数据与既有/用户改动文件会保留\n --dry-run 配合 --uninstall 时只预览将删除的文件,不实际修改\n --force 安装时覆盖已有且内容不同的文件,并保留 *.bak 备份\n`;
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 --format {json,agent,user} 输出格式(默认:user);json 用于诊断或自动化,agent 用于工作流消费\n --user-facing 等价于 --format user\n --create 兼容参数;init 默认就会创建缺失内容\n --update 按 manifest 更新 SuperSpec 管理的文件;用户改动文件保留,新的版本写入 *.new\n --uninstall 按 manifest 卸载 SuperSpec 管理的文件;.superspec 数据与既有/用户改动文件会保留\n --dry-run 配合 --uninstall 时只预览将删除的文件,不实际修改\n --force 安装时覆盖已有且内容不同的文件,并保留 *.bak 备份\n`;
13
+ }
14
+ function requestedOutputFormat(argv) {
15
+ if (argv.includes("--user-facing"))
16
+ return "user";
17
+ let selected = null;
18
+ for (let idx = 0; idx < argv.length; idx += 1) {
19
+ if (argv[idx] !== "--format")
20
+ continue;
21
+ const value = argv[idx + 1];
22
+ if (value === undefined || value.startsWith("--"))
23
+ return null;
24
+ selected = value;
25
+ }
26
+ if (selected === null)
27
+ return null;
28
+ try {
29
+ return parseDecisionOutputFormat(selected);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function fallbackOutputFormat(argv) {
36
+ return requestedOutputFormat(argv) ?? "user";
13
37
  }
14
38
  function parse_init_argv(argv) {
15
39
  const getValues = (flag) => {
@@ -45,7 +69,7 @@ function parse_init_argv(argv) {
45
69
  parseDecisionOutputFormat(value);
46
70
  const selectedFormat = argv.includes("--user-facing")
47
71
  ? "user"
48
- : (formatValues.length > 0 ? formatValues[formatValues.length - 1] : "json");
72
+ : (formatValues.length > 0 ? formatValues[formatValues.length - 1] : "user");
49
73
  const format = parseDecisionOutputFormat(selectedFormat);
50
74
  return {
51
75
  path: resolve(getValue("--path") ?? process.cwd()),
@@ -60,6 +84,118 @@ function parse_init_argv(argv) {
60
84
  function joinHomeCodex() {
61
85
  return `${homedir()}/.codex`;
62
86
  }
87
+ function scopeLabel(scope) {
88
+ return scope === "user" ? "用户级" : "项目级";
89
+ }
90
+ function modeLabel(mode, dryRun) {
91
+ if (mode === "update")
92
+ return "更新";
93
+ if (mode === "uninstall")
94
+ return dryRun ? "卸载预览" : "卸载";
95
+ return "初始化";
96
+ }
97
+ function actionSummary(actions) {
98
+ if (!Array.isArray(actions) || actions.length === 0)
99
+ return "";
100
+ const counts = new Map();
101
+ for (const item of actions) {
102
+ if (!item || typeof item !== "object")
103
+ continue;
104
+ const status = String(item.status ?? "");
105
+ if (!status)
106
+ continue;
107
+ counts.set(status, (counts.get(status) ?? 0) + 1);
108
+ }
109
+ const changedOrder = ["created", "updated", "removed", "would_remove", "skipped", "failed"];
110
+ const parts = changedOrder
111
+ .map((status) => [status, counts.get(status) ?? 0])
112
+ .filter(([, count]) => count > 0)
113
+ .map(([status, count]) => `${action_status_zh(status)} ${count} 项`);
114
+ if (parts.length > 0)
115
+ return parts.join(",");
116
+ const okCount = counts.get("ok") ?? 0;
117
+ return okCount > 0 ? `已检查 ${okCount} 项,无需改动` : "";
118
+ }
119
+ function actionTarget(action) {
120
+ const match = action.match(/^(?:install|update|uninstall(?: rmdir)?)\s+(.+)$/u);
121
+ return match?.[1] ?? "";
122
+ }
123
+ function skippedActionNotes(actions) {
124
+ if (!Array.isArray(actions))
125
+ return [];
126
+ const groups = new Map();
127
+ for (const item of actions) {
128
+ if (!item || typeof item !== "object")
129
+ continue;
130
+ const action = String(item.action ?? "");
131
+ const status = String(item.status ?? "");
132
+ if (status !== "skipped")
133
+ continue;
134
+ const detail = String(item.detail ?? "");
135
+ const note = detail ? action_detail_zh(detail) : "有文件被保留未改动;需要完整列表时添加 --format json。";
136
+ const target = actionTarget(action);
137
+ const group = groups.get(note) ?? { count: 0, targets: [] };
138
+ group.count += 1;
139
+ if (target && group.targets.length < 3)
140
+ group.targets.push(target);
141
+ groups.set(note, group);
142
+ }
143
+ return [...groups.entries()].map(([note, group]) => {
144
+ const hiddenCount = group.count - group.targets.length;
145
+ if (group.targets.length === 0)
146
+ return `${note}(${group.count} 项)`;
147
+ const suffix = hiddenCount > 0 ? ` 等 ${group.count} 项` : "";
148
+ return `${note}(${group.targets.join(",")}${suffix})`;
149
+ });
150
+ }
151
+ function knownInitProblemMessageZh(message) {
152
+ if (/install manifest missing; run superspec init before --update/iu.test(message)) {
153
+ return "缺少安装清单;请先运行 superspec init,再执行 update。";
154
+ }
155
+ if (/install manifest missing; nothing to uninstall/iu.test(message)) {
156
+ return "缺少安装清单;当前没有可卸载的 SuperSpec 管理文件。";
157
+ }
158
+ return null;
159
+ }
160
+ function reasonTextZh(item) {
161
+ const code = String(item.code ?? "");
162
+ const message = String(item.message ?? "");
163
+ const refs = Array.isArray(item.refs) ? item.refs.map((ref) => String(ref)) : [];
164
+ return knownInitProblemMessageZh(message) ?? reason_message_zh(code, message, refs);
165
+ }
166
+ function renderInitUserDecision(decision, opts = {}) {
167
+ const lines = [];
168
+ const op = modeLabel(opts.mode, opts.dryRun);
169
+ lines.push(`SuperSpec ${op}${decision.allowed ? "完成" : "未完成"}。`);
170
+ lines.push(`范围:${scopeLabel(opts.scope)}。`);
171
+ const root = opts.targetRoot ?? String(decision.install_root ?? decision.project_root ?? "");
172
+ if (root)
173
+ lines.push(`位置:${root}`);
174
+ const summary = actionSummary(decision.actions);
175
+ if (decision.allowed && summary)
176
+ lines.push(`处理:${summary}。`);
177
+ const skippedNotes = skippedActionNotes(decision.actions);
178
+ if (decision.allowed && skippedNotes.length > 0) {
179
+ lines.push("跳过项:");
180
+ for (const note of skippedNotes)
181
+ lines.push(`- ${note}`);
182
+ }
183
+ const reasons = Array.isArray(decision.block_reasons) ? decision.block_reasons : [];
184
+ if (!decision.allowed && reasons.length > 0) {
185
+ lines.push("原因:");
186
+ for (const item of reasons)
187
+ lines.push(`- ${reasonTextZh(item)}`);
188
+ lines.push("修正后重新运行同一命令;需要完整诊断时添加 --format json。");
189
+ }
190
+ return `${lines.join("\n")}\n`;
191
+ }
192
+ function printInitDecision(decision, opts) {
193
+ if (opts.format === "user") {
194
+ process.stdout.write(renderInitUserDecision(decision, opts));
195
+ return;
196
+ }
197
+ printDecision(decision, { command: opts.command ?? "init", format: opts.format });
198
+ }
63
199
  function commandFailure(proc) {
64
200
  const output = (proc.error?.message ?? (proc.stderr || proc.stdout)).trim();
65
201
  if (output)
@@ -156,12 +292,19 @@ function openspecPreflightBlocked(args, scope, installResult) {
156
292
  const decision = block("project", "openspec_preflight", [reason(code, message)], {
157
293
  next_actions: [`install or upgrade @fission-ai/openspec >= ${REQUIRED_OPENSPEC_MIN_VERSION}, then rerun \`superspec init --scope ${scope}\``],
158
294
  });
159
- printDecision({
295
+ printInitDecision({
160
296
  ...decision,
161
297
  project_root: args.path,
162
298
  install_scope: scope,
163
299
  install_root: targetRoot,
164
- }, { command: "init", format: args.format });
300
+ }, {
301
+ command: "init",
302
+ format: args.format,
303
+ mode: args.mode,
304
+ scope,
305
+ targetRoot,
306
+ dryRun: args.dryRun,
307
+ });
165
308
  return 1;
166
309
  }
167
310
  function run_init(args, scope) {
@@ -188,7 +331,14 @@ function run_init(args, scope) {
188
331
  }
189
332
  summary.install_scope = scope;
190
333
  summary.install_root = targetRoot;
191
- printDecision(summary, { command: "init", format: args.format });
334
+ printInitDecision(summary, {
335
+ command: "init",
336
+ format: args.format,
337
+ mode: args.mode,
338
+ scope,
339
+ targetRoot,
340
+ dryRun: args.dryRun,
341
+ });
192
342
  return summary.allowed ? 0 : 1;
193
343
  }
194
344
  export function main_init(argv = process.argv.slice(2)) {
@@ -208,7 +358,14 @@ export function main_init(argv = process.argv.slice(2)) {
208
358
  catch (err) {
209
359
  const change = "project";
210
360
  const errReason = err instanceof GuardError ? reason("guard_error", err.message) : reason("guard_internal_error", `${err.name}: ${err.message}`);
211
- printDecision(block(change, "guard_error", [errReason]), { command: "init", format: args?.format });
361
+ printInitDecision(block(change, "guard_error", [errReason]), {
362
+ command: "init",
363
+ format: args?.format ?? fallbackOutputFormat(argv),
364
+ mode: args?.mode,
365
+ scope: args?.scope ?? "project",
366
+ targetRoot: args?.path,
367
+ dryRun: args?.dryRun,
368
+ });
212
369
  return 2;
213
370
  }
214
371
  }
@@ -229,7 +386,14 @@ export async function main_init_async(argv = process.argv.slice(2)) {
229
386
  catch (err) {
230
387
  const change = "project";
231
388
  const errReason = err instanceof GuardError ? reason("guard_error", err.message) : reason("guard_internal_error", `${err.name}: ${err.message}`);
232
- printDecision(block(change, "guard_error", [errReason]), { command: "init", format: args?.format });
389
+ printInitDecision(block(change, "guard_error", [errReason]), {
390
+ command: "init",
391
+ format: args?.format ?? fallbackOutputFormat(argv),
392
+ mode: args?.mode,
393
+ scope: args?.scope ?? "project",
394
+ targetRoot: args?.path,
395
+ dryRun: args?.dryRun,
396
+ });
233
397
  return 2;
234
398
  }
235
399
  }
package/dist/superspec.js CHANGED
@@ -30,7 +30,7 @@ function help() {
30
30
  " version print SuperSpec CLI version",
31
31
  "",
32
32
  "examples:",
33
- " superspec init --scope project --format agent",
33
+ " superspec init --scope project",
34
34
  " superspec init --scope user",
35
35
  " superspec guard check-init --change <change> --format agent",
36
36
  " superspec doctor",
@@ -49,7 +49,7 @@ function updateHelp() {
49
49
  " --user, --global equivalent to --scope user",
50
50
  " --path PATH project root for project scope (default: current directory)",
51
51
  " --codex-home PATH Codex user home for user scope (default: $CODEX_HOME or ~/.codex)",
52
- " --format {json,agent,user} output format; use agent for workflow consumption",
52
+ " --format {json,agent,user} output format (default: user); use json for diagnostics/automation",
53
53
  " --local-only skip npm self-update and use the currently installed package",
54
54
  " -h, --help show this help",
55
55
  "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterxiaoyang/superspec",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "SuperSpec workflow package: guard runtime, generic workflow templates, and Codex adapter payload.",
5
5
  "repository": {
6
6
  "type": "git",