@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.
- package/LICENSE +22 -0
- package/README.md +461 -0
- package/README.zh.md +434 -0
- package/bin/zhuanspec.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +356 -0
- package/dist/commands/artifact-workflow.d.ts +13 -0
- package/dist/commands/artifact-workflow.js +916 -0
- package/dist/commands/change.d.ts +35 -0
- package/dist/commands/change.js +277 -0
- package/dist/commands/completion.d.ts +72 -0
- package/dist/commands/completion.js +221 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +198 -0
- package/dist/commands/show.d.ts +14 -0
- package/dist/commands/show.js +132 -0
- package/dist/commands/spec.d.ts +15 -0
- package/dist/commands/spec.js +225 -0
- package/dist/commands/validate.d.ts +24 -0
- package/dist/commands/validate.js +294 -0
- package/dist/core/archive.d.ts +30 -0
- package/dist/core/archive.js +438 -0
- package/dist/core/artifact-graph/graph.d.ts +56 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +7 -0
- package/dist/core/artifact-graph/index.js +13 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +134 -0
- package/dist/core/artifact-graph/instruction-loader.js +180 -0
- package/dist/core/artifact-graph/resolver.d.ts +61 -0
- package/dist/core/artifact-graph/resolver.js +187 -0
- package/dist/core/artifact-graph/schema.d.ts +13 -0
- package/dist/core/artifact-graph/schema.js +108 -0
- package/dist/core/artifact-graph/state.d.ts +12 -0
- package/dist/core/artifact-graph/state.js +54 -0
- package/dist/core/artifact-graph/types.d.ts +45 -0
- package/dist/core/artifact-graph/types.js +43 -0
- package/dist/core/completions/command-registry.d.ts +7 -0
- package/dist/core/completions/command-registry.js +362 -0
- package/dist/core/completions/completion-provider.d.ts +60 -0
- package/dist/core/completions/completion-provider.js +102 -0
- package/dist/core/completions/factory.d.ts +51 -0
- package/dist/core/completions/factory.js +57 -0
- package/dist/core/completions/generators/zsh-generator.d.ts +58 -0
- package/dist/core/completions/generators/zsh-generator.js +319 -0
- package/dist/core/completions/installers/zsh-installer.d.ts +136 -0
- package/dist/core/completions/installers/zsh-installer.js +449 -0
- package/dist/core/completions/types.d.ts +78 -0
- package/dist/core/completions/types.js +2 -0
- package/dist/core/config-schema.d.ts +76 -0
- package/dist/core/config-schema.js +200 -0
- package/dist/core/config.d.ts +16 -0
- package/dist/core/config.js +29 -0
- package/dist/core/configurators/agents.d.ts +8 -0
- package/dist/core/configurators/agents.js +15 -0
- package/dist/core/configurators/base.d.ts +7 -0
- package/dist/core/configurators/base.js +2 -0
- package/dist/core/configurators/claude.d.ts +8 -0
- package/dist/core/configurators/claude.js +15 -0
- package/dist/core/configurators/cline.d.ts +8 -0
- package/dist/core/configurators/cline.js +15 -0
- package/dist/core/configurators/codebuddy.d.ts +8 -0
- package/dist/core/configurators/codebuddy.js +15 -0
- package/dist/core/configurators/costrict.d.ts +8 -0
- package/dist/core/configurators/costrict.js +15 -0
- package/dist/core/configurators/iflow.d.ts +8 -0
- package/dist/core/configurators/iflow.js +15 -0
- package/dist/core/configurators/qoder.d.ts +30 -0
- package/dist/core/configurators/qoder.js +42 -0
- package/dist/core/configurators/qwen.d.ts +24 -0
- package/dist/core/configurators/qwen.js +37 -0
- package/dist/core/configurators/registry.d.ts +9 -0
- package/dist/core/configurators/registry.js +43 -0
- package/dist/core/configurators/slash/amazon-q.d.ts +9 -0
- package/dist/core/configurators/slash/amazon-q.js +46 -0
- package/dist/core/configurators/slash/antigravity.d.ts +9 -0
- package/dist/core/configurators/slash/antigravity.js +23 -0
- package/dist/core/configurators/slash/auggie.d.ts +9 -0
- package/dist/core/configurators/slash/auggie.js +31 -0
- package/dist/core/configurators/slash/base.d.ts +19 -0
- package/dist/core/configurators/slash/base.js +69 -0
- package/dist/core/configurators/slash/claude.d.ts +9 -0
- package/dist/core/configurators/slash/claude.js +37 -0
- package/dist/core/configurators/slash/cline.d.ts +9 -0
- package/dist/core/configurators/slash/cline.js +23 -0
- package/dist/core/configurators/slash/codebuddy.d.ts +9 -0
- package/dist/core/configurators/slash/codebuddy.js +37 -0
- package/dist/core/configurators/slash/codex.d.ts +14 -0
- package/dist/core/configurators/slash/codex.js +109 -0
- package/dist/core/configurators/slash/costrict.d.ts +9 -0
- package/dist/core/configurators/slash/costrict.js +31 -0
- package/dist/core/configurators/slash/crush.d.ts +9 -0
- package/dist/core/configurators/slash/crush.js +37 -0
- package/dist/core/configurators/slash/cursor.d.ts +9 -0
- package/dist/core/configurators/slash/cursor.js +37 -0
- package/dist/core/configurators/slash/factory.d.ts +10 -0
- package/dist/core/configurators/slash/factory.js +35 -0
- package/dist/core/configurators/slash/gemini.d.ts +9 -0
- package/dist/core/configurators/slash/gemini.js +22 -0
- package/dist/core/configurators/slash/github-copilot.d.ts +9 -0
- package/dist/core/configurators/slash/github-copilot.js +34 -0
- package/dist/core/configurators/slash/iflow.d.ts +9 -0
- package/dist/core/configurators/slash/iflow.js +37 -0
- package/dist/core/configurators/slash/kilocode.d.ts +9 -0
- package/dist/core/configurators/slash/kilocode.js +17 -0
- package/dist/core/configurators/slash/opencode.d.ts +12 -0
- package/dist/core/configurators/slash/opencode.js +72 -0
- package/dist/core/configurators/slash/qoder.d.ts +35 -0
- package/dist/core/configurators/slash/qoder.js +76 -0
- package/dist/core/configurators/slash/qwen.d.ts +32 -0
- package/dist/core/configurators/slash/qwen.js +49 -0
- package/dist/core/configurators/slash/registry.d.ts +8 -0
- package/dist/core/configurators/slash/registry.js +75 -0
- package/dist/core/configurators/slash/roocode.d.ts +9 -0
- package/dist/core/configurators/slash/roocode.js +23 -0
- package/dist/core/configurators/slash/toml-base.d.ts +10 -0
- package/dist/core/configurators/slash/toml-base.js +53 -0
- package/dist/core/configurators/slash/windsurf.d.ts +9 -0
- package/dist/core/configurators/slash/windsurf.js +23 -0
- package/dist/core/converters/json-converter.d.ts +6 -0
- package/dist/core/converters/json-converter.js +51 -0
- package/dist/core/global-config.d.ts +39 -0
- package/dist/core/global-config.js +115 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +3 -0
- package/dist/core/init.d.ts +60 -0
- package/dist/core/init.js +861 -0
- package/dist/core/list.d.ts +9 -0
- package/dist/core/list.js +171 -0
- package/dist/core/parsers/change-parser.d.ts +13 -0
- package/dist/core/parsers/change-parser.js +193 -0
- package/dist/core/parsers/markdown-parser.d.ts +22 -0
- package/dist/core/parsers/markdown-parser.js +187 -0
- package/dist/core/parsers/requirement-blocks.d.ts +37 -0
- package/dist/core/parsers/requirement-blocks.js +201 -0
- package/dist/core/project-config.d.ts +34 -0
- package/dist/core/project-config.js +79 -0
- package/dist/core/schemas/base.schema.d.ts +13 -0
- package/dist/core/schemas/base.schema.js +13 -0
- package/dist/core/schemas/change.schema.d.ts +73 -0
- package/dist/core/schemas/change.schema.js +31 -0
- package/dist/core/schemas/index.d.ts +4 -0
- package/dist/core/schemas/index.js +4 -0
- package/dist/core/schemas/spec.schema.d.ts +18 -0
- package/dist/core/schemas/spec.schema.js +15 -0
- package/dist/core/skill-discovery.d.ts +24 -0
- package/dist/core/skill-discovery.js +153 -0
- package/dist/core/specs-apply.d.ts +73 -0
- package/dist/core/specs-apply.js +384 -0
- package/dist/core/styles/palette.d.ts +7 -0
- package/dist/core/styles/palette.js +8 -0
- package/dist/core/templates/agents-root-stub.d.ts +2 -0
- package/dist/core/templates/agents-root-stub.js +17 -0
- package/dist/core/templates/agents-template.d.ts +2 -0
- package/dist/core/templates/agents-template.js +706 -0
- package/dist/core/templates/claude-template.d.ts +2 -0
- package/dist/core/templates/claude-template.js +2 -0
- package/dist/core/templates/cline-template.d.ts +2 -0
- package/dist/core/templates/cline-template.js +2 -0
- package/dist/core/templates/costrict-template.d.ts +2 -0
- package/dist/core/templates/costrict-template.js +2 -0
- package/dist/core/templates/index.d.ts +17 -0
- package/dist/core/templates/index.js +37 -0
- package/dist/core/templates/project-template.d.ts +8 -0
- package/dist/core/templates/project-template.js +32 -0
- package/dist/core/templates/skill-templates.d.ts +103 -0
- package/dist/core/templates/skill-templates.js +2131 -0
- package/dist/core/templates/slash-command-templates.d.ts +4 -0
- package/dist/core/templates/slash-command-templates.js +81 -0
- package/dist/core/update.d.ts +4 -0
- package/dist/core/update.js +88 -0
- package/dist/core/validation/constants.d.ts +34 -0
- package/dist/core/validation/constants.js +40 -0
- package/dist/core/validation/types.d.ts +18 -0
- package/dist/core/validation/types.js +2 -0
- package/dist/core/validation/validator.d.ts +33 -0
- package/dist/core/validation/validator.js +409 -0
- package/dist/core/view.d.ts +8 -0
- package/dist/core/view.js +168 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/utils/change-metadata.d.ts +47 -0
- package/dist/utils/change-metadata.js +130 -0
- package/dist/utils/change-utils.d.ts +51 -0
- package/dist/utils/change-utils.js +100 -0
- package/dist/utils/file-system.d.ts +19 -0
- package/dist/utils/file-system.js +177 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/interactive.d.ts +18 -0
- package/dist/utils/interactive.js +21 -0
- package/dist/utils/item-discovery.d.ts +4 -0
- package/dist/utils/item-discovery.js +72 -0
- package/dist/utils/match.d.ts +3 -0
- package/dist/utils/match.js +22 -0
- package/dist/utils/shell-detection.d.ts +20 -0
- package/dist/utils/shell-detection.js +41 -0
- package/dist/utils/task-progress.d.ts +8 -0
- package/dist/utils/task-progress.js +36 -0
- package/package.json +81 -0
- package/schemas/spec-driven/schema.yaml +205 -0
- package/schemas/spec-driven/templates/design.md +19 -0
- package/schemas/spec-driven/templates/proposal.md +43 -0
- package/schemas/spec-driven/templates/spec.md +8 -0
- package/schemas/spec-driven/templates/tasks.md +25 -0
- package/schemas/tdd/schema.yaml +213 -0
- package/schemas/tdd/templates/docs.md +0 -0
- package/schemas/tdd/templates/implementation.md +11 -0
- package/schemas/tdd/templates/spec.md +11 -0
- package/schemas/tdd/templates/test.md +11 -0
- 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
|