@zhuan-ai/zhuanspec 1.3.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.
Files changed (210) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +461 -0
  3. package/README.zh.md +434 -0
  4. package/bin/zhuanspec.js +3 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.js +356 -0
  7. package/dist/commands/artifact-workflow.d.ts +13 -0
  8. package/dist/commands/artifact-workflow.js +916 -0
  9. package/dist/commands/change.d.ts +35 -0
  10. package/dist/commands/change.js +277 -0
  11. package/dist/commands/completion.d.ts +72 -0
  12. package/dist/commands/completion.js +221 -0
  13. package/dist/commands/config.d.ts +8 -0
  14. package/dist/commands/config.js +198 -0
  15. package/dist/commands/show.d.ts +14 -0
  16. package/dist/commands/show.js +132 -0
  17. package/dist/commands/spec.d.ts +15 -0
  18. package/dist/commands/spec.js +225 -0
  19. package/dist/commands/validate.d.ts +24 -0
  20. package/dist/commands/validate.js +294 -0
  21. package/dist/core/archive.d.ts +30 -0
  22. package/dist/core/archive.js +438 -0
  23. package/dist/core/artifact-graph/graph.d.ts +56 -0
  24. package/dist/core/artifact-graph/graph.js +141 -0
  25. package/dist/core/artifact-graph/index.d.ts +7 -0
  26. package/dist/core/artifact-graph/index.js +13 -0
  27. package/dist/core/artifact-graph/instruction-loader.d.ts +134 -0
  28. package/dist/core/artifact-graph/instruction-loader.js +180 -0
  29. package/dist/core/artifact-graph/resolver.d.ts +61 -0
  30. package/dist/core/artifact-graph/resolver.js +187 -0
  31. package/dist/core/artifact-graph/schema.d.ts +13 -0
  32. package/dist/core/artifact-graph/schema.js +108 -0
  33. package/dist/core/artifact-graph/state.d.ts +12 -0
  34. package/dist/core/artifact-graph/state.js +54 -0
  35. package/dist/core/artifact-graph/types.d.ts +45 -0
  36. package/dist/core/artifact-graph/types.js +43 -0
  37. package/dist/core/completions/command-registry.d.ts +7 -0
  38. package/dist/core/completions/command-registry.js +362 -0
  39. package/dist/core/completions/completion-provider.d.ts +60 -0
  40. package/dist/core/completions/completion-provider.js +102 -0
  41. package/dist/core/completions/factory.d.ts +51 -0
  42. package/dist/core/completions/factory.js +57 -0
  43. package/dist/core/completions/generators/zsh-generator.d.ts +58 -0
  44. package/dist/core/completions/generators/zsh-generator.js +319 -0
  45. package/dist/core/completions/installers/zsh-installer.d.ts +136 -0
  46. package/dist/core/completions/installers/zsh-installer.js +449 -0
  47. package/dist/core/completions/types.d.ts +78 -0
  48. package/dist/core/completions/types.js +2 -0
  49. package/dist/core/config-schema.d.ts +76 -0
  50. package/dist/core/config-schema.js +200 -0
  51. package/dist/core/config.d.ts +16 -0
  52. package/dist/core/config.js +29 -0
  53. package/dist/core/configurators/agents.d.ts +8 -0
  54. package/dist/core/configurators/agents.js +15 -0
  55. package/dist/core/configurators/base.d.ts +7 -0
  56. package/dist/core/configurators/base.js +2 -0
  57. package/dist/core/configurators/claude.d.ts +8 -0
  58. package/dist/core/configurators/claude.js +15 -0
  59. package/dist/core/configurators/cline.d.ts +8 -0
  60. package/dist/core/configurators/cline.js +15 -0
  61. package/dist/core/configurators/codebuddy.d.ts +8 -0
  62. package/dist/core/configurators/codebuddy.js +15 -0
  63. package/dist/core/configurators/costrict.d.ts +8 -0
  64. package/dist/core/configurators/costrict.js +15 -0
  65. package/dist/core/configurators/iflow.d.ts +8 -0
  66. package/dist/core/configurators/iflow.js +15 -0
  67. package/dist/core/configurators/qoder.d.ts +30 -0
  68. package/dist/core/configurators/qoder.js +42 -0
  69. package/dist/core/configurators/qwen.d.ts +24 -0
  70. package/dist/core/configurators/qwen.js +37 -0
  71. package/dist/core/configurators/registry.d.ts +9 -0
  72. package/dist/core/configurators/registry.js +43 -0
  73. package/dist/core/configurators/slash/amazon-q.d.ts +9 -0
  74. package/dist/core/configurators/slash/amazon-q.js +46 -0
  75. package/dist/core/configurators/slash/antigravity.d.ts +9 -0
  76. package/dist/core/configurators/slash/antigravity.js +23 -0
  77. package/dist/core/configurators/slash/auggie.d.ts +9 -0
  78. package/dist/core/configurators/slash/auggie.js +31 -0
  79. package/dist/core/configurators/slash/base.d.ts +19 -0
  80. package/dist/core/configurators/slash/base.js +69 -0
  81. package/dist/core/configurators/slash/claude.d.ts +9 -0
  82. package/dist/core/configurators/slash/claude.js +37 -0
  83. package/dist/core/configurators/slash/cline.d.ts +9 -0
  84. package/dist/core/configurators/slash/cline.js +23 -0
  85. package/dist/core/configurators/slash/codebuddy.d.ts +9 -0
  86. package/dist/core/configurators/slash/codebuddy.js +37 -0
  87. package/dist/core/configurators/slash/codex.d.ts +14 -0
  88. package/dist/core/configurators/slash/codex.js +109 -0
  89. package/dist/core/configurators/slash/costrict.d.ts +9 -0
  90. package/dist/core/configurators/slash/costrict.js +31 -0
  91. package/dist/core/configurators/slash/crush.d.ts +9 -0
  92. package/dist/core/configurators/slash/crush.js +37 -0
  93. package/dist/core/configurators/slash/cursor.d.ts +9 -0
  94. package/dist/core/configurators/slash/cursor.js +37 -0
  95. package/dist/core/configurators/slash/factory.d.ts +10 -0
  96. package/dist/core/configurators/slash/factory.js +35 -0
  97. package/dist/core/configurators/slash/gemini.d.ts +9 -0
  98. package/dist/core/configurators/slash/gemini.js +22 -0
  99. package/dist/core/configurators/slash/github-copilot.d.ts +9 -0
  100. package/dist/core/configurators/slash/github-copilot.js +34 -0
  101. package/dist/core/configurators/slash/iflow.d.ts +9 -0
  102. package/dist/core/configurators/slash/iflow.js +37 -0
  103. package/dist/core/configurators/slash/kilocode.d.ts +9 -0
  104. package/dist/core/configurators/slash/kilocode.js +17 -0
  105. package/dist/core/configurators/slash/opencode.d.ts +12 -0
  106. package/dist/core/configurators/slash/opencode.js +72 -0
  107. package/dist/core/configurators/slash/qoder.d.ts +35 -0
  108. package/dist/core/configurators/slash/qoder.js +76 -0
  109. package/dist/core/configurators/slash/qwen.d.ts +32 -0
  110. package/dist/core/configurators/slash/qwen.js +49 -0
  111. package/dist/core/configurators/slash/registry.d.ts +8 -0
  112. package/dist/core/configurators/slash/registry.js +75 -0
  113. package/dist/core/configurators/slash/roocode.d.ts +9 -0
  114. package/dist/core/configurators/slash/roocode.js +23 -0
  115. package/dist/core/configurators/slash/toml-base.d.ts +10 -0
  116. package/dist/core/configurators/slash/toml-base.js +53 -0
  117. package/dist/core/configurators/slash/windsurf.d.ts +9 -0
  118. package/dist/core/configurators/slash/windsurf.js +23 -0
  119. package/dist/core/converters/json-converter.d.ts +6 -0
  120. package/dist/core/converters/json-converter.js +51 -0
  121. package/dist/core/global-config.d.ts +39 -0
  122. package/dist/core/global-config.js +115 -0
  123. package/dist/core/index.d.ts +2 -0
  124. package/dist/core/index.js +3 -0
  125. package/dist/core/init.d.ts +60 -0
  126. package/dist/core/init.js +861 -0
  127. package/dist/core/list.d.ts +9 -0
  128. package/dist/core/list.js +171 -0
  129. package/dist/core/parsers/change-parser.d.ts +13 -0
  130. package/dist/core/parsers/change-parser.js +193 -0
  131. package/dist/core/parsers/markdown-parser.d.ts +22 -0
  132. package/dist/core/parsers/markdown-parser.js +187 -0
  133. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  134. package/dist/core/parsers/requirement-blocks.js +201 -0
  135. package/dist/core/project-config.d.ts +34 -0
  136. package/dist/core/project-config.js +79 -0
  137. package/dist/core/schemas/base.schema.d.ts +13 -0
  138. package/dist/core/schemas/base.schema.js +13 -0
  139. package/dist/core/schemas/change.schema.d.ts +73 -0
  140. package/dist/core/schemas/change.schema.js +31 -0
  141. package/dist/core/schemas/index.d.ts +4 -0
  142. package/dist/core/schemas/index.js +4 -0
  143. package/dist/core/schemas/spec.schema.d.ts +18 -0
  144. package/dist/core/schemas/spec.schema.js +15 -0
  145. package/dist/core/skill-discovery.d.ts +24 -0
  146. package/dist/core/skill-discovery.js +153 -0
  147. package/dist/core/specs-apply.d.ts +73 -0
  148. package/dist/core/specs-apply.js +384 -0
  149. package/dist/core/styles/palette.d.ts +7 -0
  150. package/dist/core/styles/palette.js +8 -0
  151. package/dist/core/templates/agents-root-stub.d.ts +2 -0
  152. package/dist/core/templates/agents-root-stub.js +17 -0
  153. package/dist/core/templates/agents-template.d.ts +2 -0
  154. package/dist/core/templates/agents-template.js +706 -0
  155. package/dist/core/templates/claude-template.d.ts +2 -0
  156. package/dist/core/templates/claude-template.js +2 -0
  157. package/dist/core/templates/cline-template.d.ts +2 -0
  158. package/dist/core/templates/cline-template.js +2 -0
  159. package/dist/core/templates/costrict-template.d.ts +2 -0
  160. package/dist/core/templates/costrict-template.js +2 -0
  161. package/dist/core/templates/index.d.ts +17 -0
  162. package/dist/core/templates/index.js +37 -0
  163. package/dist/core/templates/project-template.d.ts +8 -0
  164. package/dist/core/templates/project-template.js +32 -0
  165. package/dist/core/templates/skill-templates.d.ts +103 -0
  166. package/dist/core/templates/skill-templates.js +2131 -0
  167. package/dist/core/templates/slash-command-templates.d.ts +4 -0
  168. package/dist/core/templates/slash-command-templates.js +81 -0
  169. package/dist/core/update.d.ts +4 -0
  170. package/dist/core/update.js +88 -0
  171. package/dist/core/validation/constants.d.ts +34 -0
  172. package/dist/core/validation/constants.js +40 -0
  173. package/dist/core/validation/types.d.ts +18 -0
  174. package/dist/core/validation/types.js +2 -0
  175. package/dist/core/validation/validator.d.ts +33 -0
  176. package/dist/core/validation/validator.js +409 -0
  177. package/dist/core/view.d.ts +8 -0
  178. package/dist/core/view.js +168 -0
  179. package/dist/index.d.ts +3 -0
  180. package/dist/index.js +3 -0
  181. package/dist/utils/change-metadata.d.ts +47 -0
  182. package/dist/utils/change-metadata.js +130 -0
  183. package/dist/utils/change-utils.d.ts +51 -0
  184. package/dist/utils/change-utils.js +100 -0
  185. package/dist/utils/file-system.d.ts +19 -0
  186. package/dist/utils/file-system.js +177 -0
  187. package/dist/utils/index.d.ts +4 -0
  188. package/dist/utils/index.js +5 -0
  189. package/dist/utils/interactive.d.ts +18 -0
  190. package/dist/utils/interactive.js +21 -0
  191. package/dist/utils/item-discovery.d.ts +4 -0
  192. package/dist/utils/item-discovery.js +72 -0
  193. package/dist/utils/match.d.ts +3 -0
  194. package/dist/utils/match.js +22 -0
  195. package/dist/utils/shell-detection.d.ts +20 -0
  196. package/dist/utils/shell-detection.js +41 -0
  197. package/dist/utils/task-progress.d.ts +8 -0
  198. package/dist/utils/task-progress.js +36 -0
  199. package/package.json +81 -0
  200. package/schemas/spec-driven/schema.yaml +205 -0
  201. package/schemas/spec-driven/templates/design.md +19 -0
  202. package/schemas/spec-driven/templates/proposal.md +43 -0
  203. package/schemas/spec-driven/templates/spec.md +8 -0
  204. package/schemas/spec-driven/templates/tasks.md +25 -0
  205. package/schemas/tdd/schema.yaml +213 -0
  206. package/schemas/tdd/templates/docs.md +0 -0
  207. package/schemas/tdd/templates/implementation.md +11 -0
  208. package/schemas/tdd/templates/spec.md +11 -0
  209. package/schemas/tdd/templates/test.md +11 -0
  210. package/scripts/postinstall.js +147 -0
@@ -0,0 +1,198 @@
1
+ import { spawn } from 'node:child_process';
2
+ import * as fs from 'node:fs';
3
+ import { getGlobalConfigPath, getGlobalConfig, saveGlobalConfig, } from '../core/global-config.js';
4
+ import { getNestedValue, setNestedValue, deleteNestedValue, coerceValue, formatValueYaml, validateConfigKeyPath, validateConfig, DEFAULT_CONFIG, } from '../core/config-schema.js';
5
+ /**
6
+ * Register the config command and all its subcommands.
7
+ *
8
+ * @param program - The Commander program instance
9
+ */
10
+ export function registerConfigCommand(program) {
11
+ const configCmd = program
12
+ .command('config')
13
+ .description('View and modify global ZhuanSpec configuration')
14
+ .option('--scope <scope>', 'Config scope (only "global" supported currently)')
15
+ .hook('preAction', (thisCommand) => {
16
+ const opts = thisCommand.opts();
17
+ if (opts.scope && opts.scope !== 'global') {
18
+ console.error('错误:项目本地配置尚未实现');
19
+ process.exit(1);
20
+ }
21
+ });
22
+ // config path
23
+ configCmd
24
+ .command('path')
25
+ .description('Show config file location')
26
+ .action(() => {
27
+ console.log(getGlobalConfigPath());
28
+ });
29
+ // config list
30
+ configCmd
31
+ .command('list')
32
+ .description('Show all current settings')
33
+ .option('--json', 'Output as JSON')
34
+ .action((options) => {
35
+ const config = getGlobalConfig();
36
+ if (options.json) {
37
+ console.log(JSON.stringify(config, null, 2));
38
+ }
39
+ else {
40
+ console.log(formatValueYaml(config));
41
+ }
42
+ });
43
+ // config get
44
+ configCmd
45
+ .command('get <key>')
46
+ .description('Get a specific value (raw, scriptable)')
47
+ .action((key) => {
48
+ const config = getGlobalConfig();
49
+ const value = getNestedValue(config, key);
50
+ if (value === undefined) {
51
+ process.exitCode = 1;
52
+ return;
53
+ }
54
+ if (typeof value === 'object' && value !== null) {
55
+ console.log(JSON.stringify(value));
56
+ }
57
+ else {
58
+ console.log(String(value));
59
+ }
60
+ });
61
+ // config set
62
+ configCmd
63
+ .command('set <key> <value>')
64
+ .description('Set a value (auto-coerce types)')
65
+ .option('--string', 'Force value to be stored as string')
66
+ .option('--allow-unknown', 'Allow setting unknown keys')
67
+ .action((key, value, options) => {
68
+ const allowUnknown = Boolean(options.allowUnknown);
69
+ const keyValidation = validateConfigKeyPath(key);
70
+ if (!keyValidation.valid && !allowUnknown) {
71
+ const reason = keyValidation.reason ? ` ${keyValidation.reason}.` : '';
72
+ console.error(`错误:无效的配置键 "${key}"。${reason}`);
73
+ console.error('使用 "zhuanspec config list" 查看可用键。');
74
+ console.error('传递 --allow-unknown 以绕过此检查。');
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+ const config = getGlobalConfig();
79
+ const coercedValue = coerceValue(value, options.string || false);
80
+ // Create a copy to validate before saving
81
+ const newConfig = JSON.parse(JSON.stringify(config));
82
+ setNestedValue(newConfig, key, coercedValue);
83
+ // Validate the new config
84
+ const validation = validateConfig(newConfig);
85
+ if (!validation.success) {
86
+ console.error(`错误:无效的配置 - ${validation.error}`);
87
+ process.exitCode = 1;
88
+ return;
89
+ }
90
+ // Apply changes and save
91
+ setNestedValue(config, key, coercedValue);
92
+ saveGlobalConfig(config);
93
+ const displayValue = typeof coercedValue === 'string' ? `"${coercedValue}"` : String(coercedValue);
94
+ console.log(`已设置 ${key} = ${displayValue}`);
95
+ });
96
+ // config unset
97
+ configCmd
98
+ .command('unset <key>')
99
+ .description('Remove a key (revert to default)')
100
+ .action((key) => {
101
+ const config = getGlobalConfig();
102
+ const existed = deleteNestedValue(config, key);
103
+ if (existed) {
104
+ saveGlobalConfig(config);
105
+ console.log(`已取消设置 ${key}(已恢复为默认值)`);
106
+ }
107
+ else {
108
+ console.log(`键 "${key}" 未设置`);
109
+ }
110
+ });
111
+ // config reset
112
+ configCmd
113
+ .command('reset')
114
+ .description('Reset configuration to defaults')
115
+ .option('--all', 'Reset all configuration (required)')
116
+ .option('-y, --yes', 'Skip confirmation prompts')
117
+ .action(async (options) => {
118
+ if (!options.all) {
119
+ console.error('错误:重置需要 --all 标志');
120
+ console.error('用法:zhuanspec config reset --all [-y]');
121
+ process.exitCode = 1;
122
+ return;
123
+ }
124
+ if (!options.yes) {
125
+ const { confirm } = await import('@inquirer/prompts');
126
+ const confirmed = await confirm({
127
+ message: '将所有配置重置为默认值?',
128
+ default: false,
129
+ });
130
+ if (!confirmed) {
131
+ console.log('重置已取消。');
132
+ return;
133
+ }
134
+ }
135
+ saveGlobalConfig({ ...DEFAULT_CONFIG });
136
+ console.log('配置已重置为默认值');
137
+ });
138
+ // config edit
139
+ configCmd
140
+ .command('edit')
141
+ .description('Open config in $EDITOR')
142
+ .action(async () => {
143
+ const editor = process.env.EDITOR || process.env.VISUAL;
144
+ if (!editor) {
145
+ console.error('错误:未配置编辑器');
146
+ console.error('将 EDITOR 或 VISUAL 环境变量设置为您首选的编辑器');
147
+ console.error('示例:export EDITOR=vim');
148
+ process.exitCode = 1;
149
+ return;
150
+ }
151
+ const configPath = getGlobalConfigPath();
152
+ // Ensure config file exists with defaults
153
+ if (!fs.existsSync(configPath)) {
154
+ saveGlobalConfig({ ...DEFAULT_CONFIG });
155
+ }
156
+ // Spawn editor and wait for it to close
157
+ // Avoid shell parsing to correctly handle paths with spaces in both
158
+ // the editor path and config path
159
+ const child = spawn(editor, [configPath], {
160
+ stdio: 'inherit',
161
+ shell: false,
162
+ });
163
+ await new Promise((resolve, reject) => {
164
+ child.on('close', (code) => {
165
+ if (code === 0) {
166
+ resolve();
167
+ }
168
+ else {
169
+ reject(new Error(`Editor exited with code ${code}`));
170
+ }
171
+ });
172
+ child.on('error', reject);
173
+ });
174
+ try {
175
+ const rawConfig = fs.readFileSync(configPath, 'utf-8');
176
+ const parsedConfig = JSON.parse(rawConfig);
177
+ const validation = validateConfig(parsedConfig);
178
+ if (!validation.success) {
179
+ console.error(`错误:无效的配置 - ${validation.error}`);
180
+ process.exitCode = 1;
181
+ }
182
+ }
183
+ catch (error) {
184
+ if (error.code === 'ENOENT') {
185
+ console.error(`错误:在 ${configPath} 未找到配置文件`);
186
+ }
187
+ else if (error instanceof SyntaxError) {
188
+ console.error(`错误:${configPath} 中的 JSON 无效`);
189
+ console.error(error.message);
190
+ }
191
+ else {
192
+ console.error(`错误:无法验证配置 - ${error instanceof Error ? error.message : String(error)}`);
193
+ }
194
+ process.exitCode = 1;
195
+ }
196
+ });
197
+ }
198
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,14 @@
1
+ export declare class ShowCommand {
2
+ execute(itemName?: string, options?: {
3
+ json?: boolean;
4
+ type?: string;
5
+ noInteractive?: boolean;
6
+ [k: string]: any;
7
+ }): Promise<void>;
8
+ private normalizeType;
9
+ private runInteractiveByType;
10
+ private showDirect;
11
+ private printNonInteractiveHint;
12
+ private warnIrrelevantFlags;
13
+ }
14
+ //# sourceMappingURL=show.d.ts.map
@@ -0,0 +1,132 @@
1
+ import { isInteractive } from '../utils/interactive.js';
2
+ import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
3
+ import { ChangeCommand } from './change.js';
4
+ import { SpecCommand } from './spec.js';
5
+ import { nearestMatches } from '../utils/match.js';
6
+ const CHANGE_FLAG_KEYS = new Set(['deltasOnly', 'requirementsOnly']);
7
+ const SPEC_FLAG_KEYS = new Set(['requirements', 'scenarios', 'requirement']);
8
+ export class ShowCommand {
9
+ async execute(itemName, options = {}) {
10
+ const interactive = isInteractive(options);
11
+ const typeOverride = this.normalizeType(options.type);
12
+ if (!itemName) {
13
+ if (interactive) {
14
+ const { select } = await import('@inquirer/prompts');
15
+ const type = await select({
16
+ message: '您想显示什么?',
17
+ choices: [
18
+ { name: 'Change', value: 'change' },
19
+ { name: 'Spec', value: 'spec' },
20
+ ],
21
+ });
22
+ await this.runInteractiveByType(type, options);
23
+ return;
24
+ }
25
+ this.printNonInteractiveHint();
26
+ process.exitCode = 1;
27
+ return;
28
+ }
29
+ await this.showDirect(itemName, { typeOverride, options });
30
+ }
31
+ normalizeType(value) {
32
+ if (!value)
33
+ return undefined;
34
+ const v = value.toLowerCase();
35
+ if (v === 'change' || v === 'spec')
36
+ return v;
37
+ return undefined;
38
+ }
39
+ async runInteractiveByType(type, options) {
40
+ const { select } = await import('@inquirer/prompts');
41
+ if (type === 'change') {
42
+ const changes = await getActiveChangeIds();
43
+ if (changes.length === 0) {
44
+ console.error('未找到变更。');
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+ const picked = await select({ message: '选择变更', choices: changes.map(id => ({ name: id, value: id })) });
49
+ const cmd = new ChangeCommand();
50
+ await cmd.show(picked, options);
51
+ return;
52
+ }
53
+ const specs = await getSpecIds();
54
+ if (specs.length === 0) {
55
+ console.error('未找到规范。');
56
+ process.exitCode = 1;
57
+ return;
58
+ }
59
+ const picked = await select({ message: '选择规范', choices: specs.map(id => ({ name: id, value: id })) });
60
+ const cmd = new SpecCommand();
61
+ await cmd.show(picked, options);
62
+ }
63
+ async showDirect(itemName, params) {
64
+ // Optimize lookups when type is pre-specified
65
+ let isChange = false;
66
+ let isSpec = false;
67
+ let changes = [];
68
+ let specs = [];
69
+ if (params.typeOverride === 'change') {
70
+ changes = await getActiveChangeIds();
71
+ isChange = changes.includes(itemName);
72
+ }
73
+ else if (params.typeOverride === 'spec') {
74
+ specs = await getSpecIds();
75
+ isSpec = specs.includes(itemName);
76
+ }
77
+ else {
78
+ [changes, specs] = await Promise.all([getActiveChangeIds(), getSpecIds()]);
79
+ isChange = changes.includes(itemName);
80
+ isSpec = specs.includes(itemName);
81
+ }
82
+ const resolvedType = params.typeOverride ?? (isChange ? 'change' : isSpec ? 'spec' : undefined);
83
+ if (!resolvedType) {
84
+ console.error(`未知项目 '${itemName}'`);
85
+ const suggestions = nearestMatches(itemName, [...changes, ...specs]);
86
+ if (suggestions.length)
87
+ console.error(`您是指:${suggestions.join(', ')}?`);
88
+ process.exitCode = 1;
89
+ return;
90
+ }
91
+ if (!params.typeOverride && isChange && isSpec) {
92
+ console.error(`项目 '${itemName}' 既匹配变更又匹配规范,存在歧义。`);
93
+ console.error('请传递 --type change|spec,或使用:zhuanspec change show / zhuanspec spec show');
94
+ process.exitCode = 1;
95
+ return;
96
+ }
97
+ this.warnIrrelevantFlags(resolvedType, params.options);
98
+ if (resolvedType === 'change') {
99
+ const cmd = new ChangeCommand();
100
+ await cmd.show(itemName, params.options);
101
+ return;
102
+ }
103
+ const cmd = new SpecCommand();
104
+ await cmd.show(itemName, params.options);
105
+ }
106
+ printNonInteractiveHint() {
107
+ console.error('没有可显示的内容。请尝试以下之一:');
108
+ console.error(' zhuanspec show <item>');
109
+ console.error(' zhuanspec change show');
110
+ console.error(' zhuanspec spec show');
111
+ console.error('或在交互式终端中运行。');
112
+ }
113
+ warnIrrelevantFlags(type, options) {
114
+ const irrelevant = [];
115
+ if (type === 'change') {
116
+ for (const k of SPEC_FLAG_KEYS)
117
+ if (k in options)
118
+ irrelevant.push(k);
119
+ }
120
+ else {
121
+ for (const k of CHANGE_FLAG_KEYS)
122
+ if (k in options)
123
+ irrelevant.push(k);
124
+ }
125
+ if (irrelevant.length > 0) {
126
+ console.error(`警告:忽略不适用于 ${type} 的标志:${irrelevant.join(', ')}`);
127
+ return true;
128
+ }
129
+ return false;
130
+ }
131
+ }
132
+ //# sourceMappingURL=show.js.map
@@ -0,0 +1,15 @@
1
+ import { program } from 'commander';
2
+ interface ShowOptions {
3
+ json?: boolean;
4
+ requirements?: boolean;
5
+ scenarios?: boolean;
6
+ requirement?: string;
7
+ noInteractive?: boolean;
8
+ }
9
+ export declare class SpecCommand {
10
+ private SPECS_DIR;
11
+ show(specId?: string, options?: ShowOptions): Promise<void>;
12
+ }
13
+ export declare function registerSpecCommand(rootProgram: typeof program): import("commander").Command;
14
+ export {};
15
+ //# sourceMappingURL=spec.d.ts.map
@@ -0,0 +1,225 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { MarkdownParser } from '../core/parsers/markdown-parser.js';
4
+ import { Validator } from '../core/validation/validator.js';
5
+ import { isInteractive } from '../utils/interactive.js';
6
+ import { getSpecIds } from '../utils/item-discovery.js';
7
+ const SPECS_DIR = 'zhuanspec/specs';
8
+ function parseSpecFromFile(specPath, specId) {
9
+ const content = readFileSync(specPath, 'utf-8');
10
+ const parser = new MarkdownParser(content);
11
+ return parser.parseSpec(specId);
12
+ }
13
+ function validateRequirementIndex(spec, requirementOpt) {
14
+ if (!requirementOpt)
15
+ return undefined;
16
+ const index = Number.parseInt(requirementOpt, 10);
17
+ if (!Number.isInteger(index) || index < 1 || index > spec.requirements.length) {
18
+ throw new Error(`Requirement ${requirementOpt} not found`);
19
+ }
20
+ return index - 1; // convert to 0-based
21
+ }
22
+ function filterSpec(spec, options) {
23
+ const requirementIndex = validateRequirementIndex(spec, options.requirement);
24
+ const includeScenarios = options.scenarios !== false && !options.requirements;
25
+ const filteredRequirements = (requirementIndex !== undefined
26
+ ? [spec.requirements[requirementIndex]]
27
+ : spec.requirements).map(req => ({
28
+ text: req.text,
29
+ scenarios: includeScenarios ? req.scenarios : [],
30
+ }));
31
+ const metadata = spec.metadata ?? { version: '1.0.0', format: 'zhuanspec' };
32
+ return {
33
+ name: spec.name,
34
+ overview: spec.overview,
35
+ requirements: filteredRequirements,
36
+ metadata,
37
+ };
38
+ }
39
+ /**
40
+ * Print the raw markdown content for a spec file without any formatting.
41
+ * Raw-first behavior ensures text mode is a passthrough for deterministic output.
42
+ */
43
+ function printSpecTextRaw(specPath) {
44
+ const content = readFileSync(specPath, 'utf-8');
45
+ console.log(content);
46
+ }
47
+ export class SpecCommand {
48
+ SPECS_DIR = 'zhuanspec/specs';
49
+ async show(specId, options = {}) {
50
+ if (!specId) {
51
+ const canPrompt = isInteractive(options);
52
+ const specIds = await getSpecIds();
53
+ if (canPrompt && specIds.length > 0) {
54
+ const { select } = await import('@inquirer/prompts');
55
+ specId = await select({
56
+ message: '选择要显示的规范',
57
+ choices: specIds.map(id => ({ name: id, value: id })),
58
+ });
59
+ }
60
+ else {
61
+ throw new Error('Missing required argument <spec-id>');
62
+ }
63
+ }
64
+ const specPath = join(this.SPECS_DIR, specId, 'spec.md');
65
+ if (!existsSync(specPath)) {
66
+ throw new Error(`Spec '${specId}' not found at zhuanspec/specs/${specId}/spec.md`);
67
+ }
68
+ if (options.json) {
69
+ if (options.requirements && options.requirement) {
70
+ throw new Error('Options --requirements and --requirement cannot be used together');
71
+ }
72
+ const parsed = parseSpecFromFile(specPath, specId);
73
+ const filtered = filterSpec(parsed, options);
74
+ const output = {
75
+ id: specId,
76
+ title: parsed.name,
77
+ overview: parsed.overview,
78
+ requirementCount: filtered.requirements.length,
79
+ requirements: filtered.requirements,
80
+ metadata: parsed.metadata ?? { version: '1.0.0', format: 'zhuanspec' },
81
+ };
82
+ console.log(JSON.stringify(output, null, 2));
83
+ return;
84
+ }
85
+ printSpecTextRaw(specPath);
86
+ }
87
+ }
88
+ export function registerSpecCommand(rootProgram) {
89
+ const specCommand = rootProgram
90
+ .command('spec')
91
+ .description('Manage and view ZhuanSpec specifications');
92
+ // Deprecation notice for noun-based commands
93
+ specCommand.hook('preAction', () => {
94
+ console.error('Warning: The "zhuanspec spec ..." commands are deprecated. Prefer verb-first commands (e.g., "zhuanspec show", "zhuanspec validate --specs").');
95
+ });
96
+ specCommand
97
+ .command('show [spec-id]')
98
+ .description('Display a specific specification')
99
+ .option('--json', 'Output as JSON')
100
+ .option('--requirements', 'JSON only: Show only requirements (exclude scenarios)')
101
+ .option('--no-scenarios', 'JSON only: Exclude scenario content')
102
+ .option('-r, --requirement <id>', 'JSON only: Show specific requirement by ID (1-based)')
103
+ .option('--no-interactive', 'Disable interactive prompts')
104
+ .action(async (specId, options) => {
105
+ try {
106
+ const cmd = new SpecCommand();
107
+ await cmd.show(specId, options);
108
+ }
109
+ catch (error) {
110
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
111
+ process.exitCode = 1;
112
+ }
113
+ });
114
+ specCommand
115
+ .command('list')
116
+ .description('List all available specifications')
117
+ .option('--json', 'Output as JSON')
118
+ .option('--long', 'Show id and title with counts')
119
+ .action((options) => {
120
+ try {
121
+ if (!existsSync(SPECS_DIR)) {
122
+ console.log('No items found');
123
+ return;
124
+ }
125
+ const specs = readdirSync(SPECS_DIR, { withFileTypes: true })
126
+ .filter(dirent => dirent.isDirectory())
127
+ .map(dirent => {
128
+ const specPath = join(SPECS_DIR, dirent.name, 'spec.md');
129
+ if (existsSync(specPath)) {
130
+ try {
131
+ const spec = parseSpecFromFile(specPath, dirent.name);
132
+ return {
133
+ id: dirent.name,
134
+ title: spec.name,
135
+ requirementCount: spec.requirements.length
136
+ };
137
+ }
138
+ catch {
139
+ return {
140
+ id: dirent.name,
141
+ title: dirent.name,
142
+ requirementCount: 0
143
+ };
144
+ }
145
+ }
146
+ return null;
147
+ })
148
+ .filter((spec) => spec !== null)
149
+ .sort((a, b) => a.id.localeCompare(b.id));
150
+ if (options.json) {
151
+ console.log(JSON.stringify(specs, null, 2));
152
+ }
153
+ else {
154
+ if (specs.length === 0) {
155
+ console.log('No items found');
156
+ return;
157
+ }
158
+ if (!options.long) {
159
+ specs.forEach(spec => console.log(spec.id));
160
+ return;
161
+ }
162
+ specs.forEach(spec => {
163
+ console.log(`${spec.id}: ${spec.title} [requirements ${spec.requirementCount}]`);
164
+ });
165
+ }
166
+ }
167
+ catch (error) {
168
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
169
+ process.exitCode = 1;
170
+ }
171
+ });
172
+ specCommand
173
+ .command('validate [spec-id]')
174
+ .description('Validate a specification structure')
175
+ .option('--strict', 'Enable strict validation mode')
176
+ .option('--json', 'Output validation report as JSON')
177
+ .option('--no-interactive', 'Disable interactive prompts')
178
+ .action(async (specId, options) => {
179
+ try {
180
+ if (!specId) {
181
+ const canPrompt = isInteractive(options);
182
+ const specIds = await getSpecIds();
183
+ if (canPrompt && specIds.length > 0) {
184
+ const { select } = await import('@inquirer/prompts');
185
+ specId = await select({
186
+ message: '选择要验证的规范',
187
+ choices: specIds.map(id => ({ name: id, value: id })),
188
+ });
189
+ }
190
+ else {
191
+ throw new Error('Missing required argument <spec-id>');
192
+ }
193
+ }
194
+ const specPath = join(SPECS_DIR, specId, 'spec.md');
195
+ if (!existsSync(specPath)) {
196
+ throw new Error(`Spec '${specId}' not found at zhuanspec/specs/${specId}/spec.md`);
197
+ }
198
+ const validator = new Validator(options.strict);
199
+ const report = await validator.validateSpec(specPath);
200
+ if (options.json) {
201
+ console.log(JSON.stringify(report, null, 2));
202
+ }
203
+ else {
204
+ if (report.valid) {
205
+ console.log(`Specification '${specId}' is valid`);
206
+ }
207
+ else {
208
+ console.error(`Specification '${specId}' has issues`);
209
+ report.issues.forEach(issue => {
210
+ const label = issue.level === 'ERROR' ? 'ERROR' : issue.level;
211
+ const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
212
+ console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
213
+ });
214
+ }
215
+ }
216
+ process.exitCode = report.valid ? 0 : 1;
217
+ }
218
+ catch (error) {
219
+ console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
220
+ process.exitCode = 1;
221
+ }
222
+ });
223
+ return specCommand;
224
+ }
225
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1,24 @@
1
+ interface ExecuteOptions {
2
+ all?: boolean;
3
+ changes?: boolean;
4
+ specs?: boolean;
5
+ type?: string;
6
+ strict?: boolean;
7
+ json?: boolean;
8
+ noInteractive?: boolean;
9
+ interactive?: boolean;
10
+ concurrency?: string;
11
+ }
12
+ export declare class ValidateCommand {
13
+ execute(itemName: string | undefined, options?: ExecuteOptions): Promise<void>;
14
+ private normalizeType;
15
+ private runInteractiveSelector;
16
+ private printNonInteractiveHint;
17
+ private validateDirectItem;
18
+ private validateByType;
19
+ private printReport;
20
+ private printNextSteps;
21
+ private runBulkValidation;
22
+ }
23
+ export {};
24
+ //# sourceMappingURL=validate.d.ts.map