@zhuan-ai/zhuanspec 2.2.4 → 2.4.8
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/bin/zhuanspec-hook.js +3 -0
- package/dist/cli/hooks.d.ts +14 -0
- package/dist/cli/hooks.js +465 -0
- package/dist/cli/index.js +100 -0
- package/dist/commands/artifact-workflow.js +15 -34
- package/dist/commands/design.d.ts +42 -0
- package/dist/commands/design.js +337 -0
- package/dist/commands/progress.d.ts +32 -0
- package/dist/commands/progress.js +278 -0
- package/dist/commands/review.d.ts +32 -0
- package/dist/commands/review.js +472 -0
- package/dist/commands/validate.d.ts +14 -0
- package/dist/commands/validate.js +161 -10
- package/dist/core/archive.d.ts +1 -0
- package/dist/core/archive.js +45 -3
- package/dist/core/completions/command-registry.js +67 -0
- package/dist/core/configurators/slash/amazon-q.js +32 -2
- package/dist/core/configurators/slash/antigravity.js +8 -2
- package/dist/core/configurators/slash/auggie.js +16 -1
- package/dist/core/configurators/slash/base.js +1 -1
- package/dist/core/configurators/slash/claude.d.ts +4 -0
- package/dist/core/configurators/slash/claude.js +56 -1
- package/dist/core/configurators/slash/cline.js +8 -2
- package/dist/core/configurators/slash/codebuddy.js +22 -1
- package/dist/core/configurators/slash/codex.js +21 -0
- package/dist/core/configurators/slash/costrict.js +15 -0
- package/dist/core/configurators/slash/crush.js +22 -1
- package/dist/core/configurators/slash/cursor.js +22 -1
- package/dist/core/configurators/slash/factory.js +16 -1
- package/dist/core/configurators/slash/gemini.js +8 -2
- package/dist/core/configurators/slash/github-copilot.js +19 -1
- package/dist/core/configurators/slash/iflow.js +22 -1
- package/dist/core/configurators/slash/kilocode.js +4 -1
- package/dist/core/configurators/slash/opencode.js +27 -0
- package/dist/core/configurators/slash/qoder.d.ts +4 -0
- package/dist/core/configurators/slash/qoder.js +59 -1
- package/dist/core/configurators/slash/qwen.js +8 -2
- package/dist/core/configurators/slash/roocode.js +8 -2
- package/dist/core/configurators/slash/windsurf.js +8 -2
- package/dist/core/dashboard/metrics.d.ts +33 -0
- package/dist/core/dashboard/metrics.js +114 -0
- package/dist/core/hooks/collect-knowledge.d.ts +16 -0
- package/dist/core/hooks/collect-knowledge.js +203 -0
- package/dist/core/hooks/context-load-hook.d.ts +25 -0
- package/dist/core/hooks/context-load-hook.js +159 -0
- package/dist/core/hooks/deviation-check.d.ts +27 -0
- package/dist/core/hooks/deviation-check.js +403 -0
- package/dist/core/hooks/deviation-handler.d.ts +43 -0
- package/dist/core/hooks/deviation-handler.js +98 -0
- package/dist/core/hooks/init.d.ts +14 -0
- package/dist/core/hooks/init.js +244 -0
- package/dist/core/hooks/notify-milestone.d.ts +14 -0
- package/dist/core/hooks/notify-milestone.js +170 -0
- package/dist/core/hooks/post-apply.d.ts +29 -0
- package/dist/core/hooks/post-apply.js +173 -0
- package/dist/core/hooks/post-archive.d.ts +7 -0
- package/dist/core/hooks/post-archive.js +208 -0
- package/dist/core/hooks/pre-apply.d.ts +34 -0
- package/dist/core/hooks/pre-apply.js +139 -0
- package/dist/core/hooks/pre-archive.d.ts +7 -0
- package/dist/core/hooks/pre-archive.js +50 -0
- package/dist/core/hooks/record-progress.d.ts +49 -0
- package/dist/core/hooks/record-progress.js +494 -0
- package/dist/core/hooks/review-hooks.d.ts +89 -0
- package/dist/core/hooks/review-hooks.js +345 -0
- package/dist/core/hooks/review-orchestrator.d.ts +40 -0
- package/dist/core/hooks/review-orchestrator.js +146 -0
- package/dist/core/hooks/summarize.d.ts +15 -0
- package/dist/core/hooks/summarize.js +282 -0
- package/dist/core/hooks/user-input-hook.d.ts +25 -0
- package/dist/core/hooks/user-input-hook.js +179 -0
- package/dist/core/init.d.ts +8 -0
- package/dist/core/init.js +251 -23
- package/dist/core/parsers/requirement-blocks.js +13 -10
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +510 -243
- package/dist/core/templates/index.d.ts +1 -0
- package/dist/core/templates/index.js +1 -0
- package/dist/core/templates/skill-templates.js +46 -152
- package/dist/core/templates/slash-command-templates.d.ts +1 -1
- package/dist/core/templates/slash-command-templates.js +352 -20
- package/dist/core/templates/tasks-template.d.ts +7 -0
- package/dist/core/templates/tasks-template.js +130 -24
- package/dist/core/templates/tdd-tasks-template.d.ts +3 -0
- package/dist/core/templates/tdd-tasks-template.js +91 -38
- package/dist/core/templates/test-cases-template.d.ts +41 -0
- package/dist/core/templates/test-cases-template.js +128 -0
- package/dist/core/validation/strict-rules.d.ts +44 -5
- package/dist/core/validation/strict-rules.js +302 -8
- package/dist/core/validation/validator.js +52 -2
- package/dist/core/view.d.ts +1 -0
- package/dist/core/view.js +60 -2
- package/dist/mcp/index.d.ts +28 -0
- package/dist/mcp/index.js +31 -0
- package/dist/utils/file-system.d.ts +1 -0
- package/dist/utils/file-system.js +11 -0
- package/dist/utils/item-discovery.js +24 -2
- package/dist/utils/phase-utils.d.ts +36 -0
- package/dist/utils/phase-utils.js +117 -0
- package/package.json +22 -23
- package/schemas/spec-driven/schema.yaml +45 -31
- package/schemas/spec-driven/templates/spec.md +142 -5
- package/schemas/spec-driven/templates/tasks.md +73 -9
package/dist/core/init.js
CHANGED
|
@@ -287,14 +287,8 @@ export class InitCommand {
|
|
|
287
287
|
await this.createDirectoryStructure(zhuanspecPath);
|
|
288
288
|
await this.ensureTemplateFiles(zhuanspecPath, config);
|
|
289
289
|
}
|
|
290
|
-
// Step 2:
|
|
291
|
-
|
|
292
|
-
const rootStubStatus = await this.configureAITools(projectPath, zhuanspecDir, config.aiTools);
|
|
293
|
-
toolSpinner.stopAndPersist({
|
|
294
|
-
symbol: PALETTE.white('▌'),
|
|
295
|
-
text: PALETTE.white('AI 工具已配置'),
|
|
296
|
-
});
|
|
297
|
-
// Step 3: Select business direction and apply architecture file
|
|
290
|
+
// Step 2: Select business direction and sync Claude assets BEFORE configureAITools
|
|
291
|
+
// This ensures .claude directory is not deleted after slash commands are generated
|
|
298
292
|
await this.promptForBusinessDirection();
|
|
299
293
|
if (this.businessDirection) {
|
|
300
294
|
const directionMetaPath = path.join(zhuanspecPath, '.business-direction');
|
|
@@ -339,6 +333,7 @@ export class InitCommand {
|
|
|
339
333
|
text: PALETTE.midGray(`未找到 ${this.businessDirection} 业务规范模板目录,保留默认模板`),
|
|
340
334
|
});
|
|
341
335
|
}
|
|
336
|
+
// Sync Claude assets BEFORE configureAITools to avoid deletion
|
|
342
337
|
const claudeAssetSpinner = this.startSpinner(`正在同步 ${this.businessDirection} 业务 Claude 规范资产...`);
|
|
343
338
|
const claudeAssetResult = this.syncBusinessClaudeAssets(projectPath);
|
|
344
339
|
const claudeMdText = this.describeClaudeAssetResult('CLAUDE.md', claudeAssetResult.claudeMd);
|
|
@@ -348,8 +343,15 @@ export class InitCommand {
|
|
|
348
343
|
text: PALETTE.white(`Claude 规范资产同步完成:${claudeMdText};${dotClaudeText}`),
|
|
349
344
|
});
|
|
350
345
|
}
|
|
346
|
+
// Step 3: Configure AI tools (after Claude assets sync to preserve generated files)
|
|
347
|
+
const toolSpinner = this.startSpinner('正在配置 AI 工具...');
|
|
348
|
+
const toolConfigResult = await this.configureAITools(projectPath, zhuanspecDir, config.aiTools);
|
|
349
|
+
toolSpinner.stopAndPersist({
|
|
350
|
+
symbol: PALETTE.white('▌'),
|
|
351
|
+
text: PALETTE.white('AI 工具已配置'),
|
|
352
|
+
});
|
|
351
353
|
// Success message
|
|
352
|
-
this.displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode, rootStubStatus);
|
|
354
|
+
this.displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode, toolConfigResult.rootStubStatus, toolConfigResult.claudeHookSummary);
|
|
353
355
|
}
|
|
354
356
|
async validate(projectPath, _zhuanspecPath) {
|
|
355
357
|
const extendMode = await FileSystemUtils.directoryExists(_zhuanspecPath);
|
|
@@ -543,10 +545,34 @@ export class InitCommand {
|
|
|
543
545
|
path.join(zhuanspecPath, 'specs'),
|
|
544
546
|
path.join(zhuanspecPath, 'changes'),
|
|
545
547
|
path.join(zhuanspecPath, 'changes', 'archive'),
|
|
548
|
+
// NEW: Project-level knowledge directory
|
|
549
|
+
path.join(zhuanspecPath, 'knowledge'),
|
|
550
|
+
path.join(zhuanspecPath, 'knowledge', 'troubleshooting'),
|
|
551
|
+
path.join(zhuanspecPath, 'knowledge', 'best-practices'),
|
|
552
|
+
path.join(zhuanspecPath, 'knowledge', 'implicit-conventions'),
|
|
546
553
|
];
|
|
547
554
|
for (const dir of directories) {
|
|
548
555
|
await FileSystemUtils.createDirectory(dir);
|
|
549
556
|
}
|
|
557
|
+
// NEW: Create initial knowledge index file if not exists
|
|
558
|
+
const indexPath = path.join(zhuanspecPath, 'knowledge', 'index.md');
|
|
559
|
+
if (!(await FileSystemUtils.fileExists(indexPath))) {
|
|
560
|
+
const knowledgeIndexContent = `# Knowledge Index
|
|
561
|
+
|
|
562
|
+
## Troubleshooting
|
|
563
|
+
|
|
564
|
+
<!-- Entries will be added by collect-knowledge hook -->
|
|
565
|
+
|
|
566
|
+
## Best Practices
|
|
567
|
+
|
|
568
|
+
<!-- Project-specific best practices -->
|
|
569
|
+
|
|
570
|
+
## Implicit Conventions
|
|
571
|
+
|
|
572
|
+
<!-- Conventions discovered during implementation -->
|
|
573
|
+
`;
|
|
574
|
+
await FileSystemUtils.writeFile(indexPath, knowledgeIndexContent);
|
|
575
|
+
}
|
|
550
576
|
}
|
|
551
577
|
async generateFiles(zhuanspecPath, config) {
|
|
552
578
|
await this.writeTemplateFiles(zhuanspecPath, config, false);
|
|
@@ -635,6 +661,11 @@ export class InitCommand {
|
|
|
635
661
|
return ['oms', 'ass'];
|
|
636
662
|
}
|
|
637
663
|
async promptForBusinessDirection() {
|
|
664
|
+
// 处理显式的跳过标记(从命令行参数或交互式选择传入)
|
|
665
|
+
if (this.businessDirection === '__skip__') {
|
|
666
|
+
this.businessDirection = undefined;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
638
669
|
if (this.businessDirection) {
|
|
639
670
|
return;
|
|
640
671
|
}
|
|
@@ -1098,7 +1129,9 @@ export class InitCommand {
|
|
|
1098
1129
|
await slashConfigurator.generateAll(projectPath, zhuanspecDir);
|
|
1099
1130
|
}
|
|
1100
1131
|
}
|
|
1101
|
-
|
|
1132
|
+
// Generate Claude Code hooks configuration
|
|
1133
|
+
const claudeHookSummary = await this.generateClaudeSettings(projectPath);
|
|
1134
|
+
return { rootStubStatus, claudeHookSummary };
|
|
1102
1135
|
}
|
|
1103
1136
|
async configureRootAgentsStub(projectPath, zhuanspecDir) {
|
|
1104
1137
|
const configurator = ToolRegistry.get('agents');
|
|
@@ -1110,7 +1143,178 @@ export class InitCommand {
|
|
|
1110
1143
|
await configurator.configure(projectPath, zhuanspecDir);
|
|
1111
1144
|
return existed ? 'updated' : 'created';
|
|
1112
1145
|
}
|
|
1113
|
-
|
|
1146
|
+
/**
|
|
1147
|
+
* Generate Claude Code hooks configuration for ZhuanSpec workflow.
|
|
1148
|
+
* Creates .claude/settings.json with hooks for SessionStart, PreToolUse, PostToolUse, and Stop.
|
|
1149
|
+
*/
|
|
1150
|
+
async generateClaudeSettings(projectPath) {
|
|
1151
|
+
const claudeDir = path.join(projectPath, '.claude');
|
|
1152
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
1153
|
+
// Ensure .claude directory exists
|
|
1154
|
+
await FileSystemUtils.createDirectory(claudeDir);
|
|
1155
|
+
// Check if settings.json already exists
|
|
1156
|
+
const existingSettings = await this.readExistingSettings(settingsPath);
|
|
1157
|
+
// Define ZhuanSpec hooks configuration
|
|
1158
|
+
const zhuanspecHooksConfig = {
|
|
1159
|
+
hooks: {
|
|
1160
|
+
SessionStart: [
|
|
1161
|
+
{
|
|
1162
|
+
hooks: [
|
|
1163
|
+
{
|
|
1164
|
+
type: 'command',
|
|
1165
|
+
command: 'zhuanspec-hook init',
|
|
1166
|
+
statusMessage: 'Initializing ZhuanSpec...',
|
|
1167
|
+
},
|
|
1168
|
+
],
|
|
1169
|
+
},
|
|
1170
|
+
],
|
|
1171
|
+
PreToolUse: [
|
|
1172
|
+
{
|
|
1173
|
+
matcher: 'Write|Edit',
|
|
1174
|
+
hooks: [
|
|
1175
|
+
{
|
|
1176
|
+
type: 'command',
|
|
1177
|
+
command: 'zhuanspec-hook deviation-check',
|
|
1178
|
+
statusMessage: 'Checking proposal compliance...',
|
|
1179
|
+
},
|
|
1180
|
+
],
|
|
1181
|
+
},
|
|
1182
|
+
],
|
|
1183
|
+
PostToolUse: [
|
|
1184
|
+
{
|
|
1185
|
+
matcher: 'Write|Edit|Read|Grep|Glob|Bash|Skill|Agent|mcp__.*',
|
|
1186
|
+
hooks: [
|
|
1187
|
+
{
|
|
1188
|
+
type: 'command',
|
|
1189
|
+
command: 'zhuanspec-hook record-progress',
|
|
1190
|
+
statusMessage: '📝 Recording progress...',
|
|
1191
|
+
async: false,
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
type: 'command',
|
|
1195
|
+
command: 'zhuanspec-hook collect-knowledge',
|
|
1196
|
+
statusMessage: '📚 Collecting knowledge...',
|
|
1197
|
+
async: false,
|
|
1198
|
+
},
|
|
1199
|
+
],
|
|
1200
|
+
},
|
|
1201
|
+
],
|
|
1202
|
+
Stop: [
|
|
1203
|
+
{
|
|
1204
|
+
hooks: [
|
|
1205
|
+
{
|
|
1206
|
+
type: 'command',
|
|
1207
|
+
command: 'zhuanspec-hook summarize',
|
|
1208
|
+
statusMessage: 'Summarizing progress...',
|
|
1209
|
+
},
|
|
1210
|
+
],
|
|
1211
|
+
},
|
|
1212
|
+
],
|
|
1213
|
+
Notification: [
|
|
1214
|
+
{
|
|
1215
|
+
hooks: [
|
|
1216
|
+
{
|
|
1217
|
+
type: 'command',
|
|
1218
|
+
command: 'zhuanspec-hook notify-milestone',
|
|
1219
|
+
statusMessage: 'Checking milestones...',
|
|
1220
|
+
},
|
|
1221
|
+
],
|
|
1222
|
+
},
|
|
1223
|
+
],
|
|
1224
|
+
},
|
|
1225
|
+
env: {
|
|
1226
|
+
ZHUANSPEC_PHASE: 'idle',
|
|
1227
|
+
ZHUANSPEC_CHANGE_ID: '',
|
|
1228
|
+
ZHUANSPEC_CURRENT_TASK: '',
|
|
1229
|
+
ZHUANSPEC_HOOKS_VERSION: '1.0.0',
|
|
1230
|
+
},
|
|
1231
|
+
};
|
|
1232
|
+
// Merge with existing settings if present
|
|
1233
|
+
const mergeResult = this.mergeSettings(existingSettings, zhuanspecHooksConfig);
|
|
1234
|
+
// Write the settings file
|
|
1235
|
+
await FileSystemUtils.writeFile(settingsPath, JSON.stringify(mergeResult.settings, null, 2));
|
|
1236
|
+
return mergeResult.summary;
|
|
1237
|
+
}
|
|
1238
|
+
async readExistingSettings(settingsPath) {
|
|
1239
|
+
try {
|
|
1240
|
+
if (await FileSystemUtils.fileExists(settingsPath)) {
|
|
1241
|
+
const content = await FileSystemUtils.readFile(settingsPath);
|
|
1242
|
+
return JSON.parse(content);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
catch {
|
|
1246
|
+
// Ignore parse errors, will create new file
|
|
1247
|
+
}
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
mergeSettings(existing, zhuanspecConfig) {
|
|
1251
|
+
const summary = {
|
|
1252
|
+
totalAddedHooks: 0,
|
|
1253
|
+
duplicateHooksSkipped: 0,
|
|
1254
|
+
};
|
|
1255
|
+
if (!existing) {
|
|
1256
|
+
const zhuanspecHooks = zhuanspecConfig.hooks;
|
|
1257
|
+
for (const hookConfigs of Object.values(zhuanspecHooks)) {
|
|
1258
|
+
summary.totalAddedHooks += Array.isArray(hookConfigs) ? hookConfigs.length : 0;
|
|
1259
|
+
}
|
|
1260
|
+
return { settings: zhuanspecConfig, summary };
|
|
1261
|
+
}
|
|
1262
|
+
// Deep merge: preserve existing settings while adding ZhuanSpec hooks
|
|
1263
|
+
const merged = { ...existing };
|
|
1264
|
+
// Merge hooks
|
|
1265
|
+
if (merged.hooks && typeof merged.hooks === 'object') {
|
|
1266
|
+
const existingHooks = merged.hooks;
|
|
1267
|
+
const zhuanspecHooks = zhuanspecConfig.hooks;
|
|
1268
|
+
// For each hook type, append ZhuanSpec hooks to existing array
|
|
1269
|
+
for (const [hookType, zhuanspecHookConfig] of Object.entries(zhuanspecHooks)) {
|
|
1270
|
+
if (existingHooks[hookType] && Array.isArray(existingHooks[hookType])) {
|
|
1271
|
+
// Append ZhuanSpec hooks to existing hooks array while de-duplicating.
|
|
1272
|
+
const existingArray = existingHooks[hookType];
|
|
1273
|
+
const zhuanspecArray = zhuanspecHookConfig;
|
|
1274
|
+
const existingSignatures = new Set(existingArray.map((item) => JSON.stringify(item)));
|
|
1275
|
+
const uniqueToAppend = [];
|
|
1276
|
+
for (const item of zhuanspecArray) {
|
|
1277
|
+
const signature = JSON.stringify(item);
|
|
1278
|
+
if (existingSignatures.has(signature)) {
|
|
1279
|
+
summary.duplicateHooksSkipped += 1;
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
existingSignatures.add(signature);
|
|
1283
|
+
uniqueToAppend.push(item);
|
|
1284
|
+
}
|
|
1285
|
+
summary.totalAddedHooks += uniqueToAppend.length;
|
|
1286
|
+
existingHooks[hookType] = [...existingArray, ...uniqueToAppend];
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
// Create new hook type with ZhuanSpec config
|
|
1290
|
+
const zhuanspecArray = zhuanspecHookConfig;
|
|
1291
|
+
summary.totalAddedHooks += Array.isArray(zhuanspecArray)
|
|
1292
|
+
? zhuanspecArray.length
|
|
1293
|
+
: 0;
|
|
1294
|
+
existingHooks[hookType] = zhuanspecHookConfig;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
else {
|
|
1299
|
+
// No existing hooks, add all ZhuanSpec hooks
|
|
1300
|
+
const zhuanspecHooks = zhuanspecConfig.hooks;
|
|
1301
|
+
for (const hookConfigs of Object.values(zhuanspecHooks)) {
|
|
1302
|
+
summary.totalAddedHooks += Array.isArray(hookConfigs) ? hookConfigs.length : 0;
|
|
1303
|
+
}
|
|
1304
|
+
merged.hooks = zhuanspecConfig.hooks;
|
|
1305
|
+
}
|
|
1306
|
+
// Merge env variables
|
|
1307
|
+
if (merged.env && typeof merged.env === 'object') {
|
|
1308
|
+
const existingEnv = merged.env;
|
|
1309
|
+
const zhuanspecEnv = zhuanspecConfig.env;
|
|
1310
|
+
merged.env = { ...existingEnv, ...zhuanspecEnv };
|
|
1311
|
+
}
|
|
1312
|
+
else {
|
|
1313
|
+
merged.env = zhuanspecConfig.env;
|
|
1314
|
+
}
|
|
1315
|
+
return { settings: merged, summary };
|
|
1316
|
+
}
|
|
1317
|
+
displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode, rootStubStatus, claudeHookSummary) {
|
|
1114
1318
|
console.log(); // Empty line for spacing
|
|
1115
1319
|
const successHeadline = extendMode
|
|
1116
1320
|
? 'ZhuanSpec 工具配置已更新!'
|
|
@@ -1150,21 +1354,27 @@ export class InitCommand {
|
|
|
1150
1354
|
console.log(PALETTE.midGray('斜杠命令在启动时加载。请重启您的编程助手'));
|
|
1151
1355
|
console.log(PALETTE.midGray('以确保新的 /zhuanspec 命令出现在命令面板中。'));
|
|
1152
1356
|
}
|
|
1357
|
+
console.log();
|
|
1358
|
+
console.log(PALETTE.white('Claude Hook 注册说明'));
|
|
1359
|
+
console.log(PALETTE.lightGray(`- 直接注册(Claude 生命周期): SessionStart(init), PreToolUse(deviation-check), PostToolUse(record-progress/collect-knowledge), Stop(summarize)`));
|
|
1360
|
+
console.log(PALETTE.lightGray('- 间接触发(阶段门禁): pre-apply/post-apply/review-* 通过上述主链路与 zhuanspec review 执行'));
|
|
1361
|
+
console.log(PALETTE.midGray(`- 本次 hooks 变更: 新增 ${claudeHookSummary.totalAddedHooks},跳过重复 ${claudeHookSummary.duplicateHooksSkipped}`));
|
|
1153
1362
|
// Get the selected tool name(s) for display
|
|
1154
1363
|
const toolName = this.formatToolNames(selectedTools);
|
|
1364
|
+
// Display workflow diagram
|
|
1365
|
+
console.log();
|
|
1366
|
+
console.log(PALETTE.white('ZhuanSpec 工作流程'));
|
|
1367
|
+
console.log(PALETTE.darkGray('─'.repeat(60)));
|
|
1368
|
+
console.log();
|
|
1369
|
+
this.displayWorkflowDiagram();
|
|
1370
|
+
console.log();
|
|
1371
|
+
console.log(PALETTE.darkGray('─'.repeat(60)));
|
|
1372
|
+
console.log();
|
|
1373
|
+
// Quick start guide
|
|
1374
|
+
console.log(PALETTE.white(`快速开始 - 在 ${toolName} 中执行:`));
|
|
1375
|
+
console.log(PALETTE.lightGray(' /zhuanspec:proposal "我想添加 [您的功能]"'));
|
|
1376
|
+
console.log(PALETTE.midGray(' → 自动创建变更提案,开始规范化开发流程'));
|
|
1155
1377
|
console.log();
|
|
1156
|
-
console.log(`下一步 - 将这些提示复制到 ${toolName}:`);
|
|
1157
|
-
console.log(chalk.gray('────────────────────────────────────────────────────────────'));
|
|
1158
|
-
console.log(PALETTE.white('1. 填充您的项目上下文:'));
|
|
1159
|
-
console.log(PALETTE.lightGray(' "请阅读 zhuanspec/project.md 并帮助我填写'));
|
|
1160
|
-
console.log(PALETTE.lightGray(' 有关我的项目、技术栈和约定的详细信息"\n'));
|
|
1161
|
-
console.log(PALETTE.white('2. 创建您的第一个变更提案:'));
|
|
1162
|
-
console.log(PALETTE.lightGray(' "我想添加 [您的功能]。请为此功能'));
|
|
1163
|
-
console.log(PALETTE.lightGray(' 创建一个 ZhuanSpec 变更提案"\n'));
|
|
1164
|
-
console.log(PALETTE.white('3. 了解 ZhuanSpec 工作流:'));
|
|
1165
|
-
console.log(PALETTE.lightGray(' "请解释 zhuanspec/AGENTS.md 中的 ZhuanSpec 工作流'));
|
|
1166
|
-
console.log(PALETTE.lightGray(' 以及我应该如何在这个项目上与您合作"'));
|
|
1167
|
-
console.log(PALETTE.darkGray('────────────────────────────────────────────────────────────\n'));
|
|
1168
1378
|
// Codex heads-up: prompts installed globally
|
|
1169
1379
|
const selectedToolIds = new Set(selectedTools.map((t) => t.value));
|
|
1170
1380
|
if (selectedToolIds.has('codex')) {
|
|
@@ -1173,6 +1383,24 @@ export class InitCommand {
|
|
|
1173
1383
|
console.log();
|
|
1174
1384
|
}
|
|
1175
1385
|
}
|
|
1386
|
+
displayWorkflowDiagram() {
|
|
1387
|
+
const gray = '\x1b[90m';
|
|
1388
|
+
const green = '\x1b[32m';
|
|
1389
|
+
const cyan = '\x1b[36m';
|
|
1390
|
+
const yellow = '\x1b[33m';
|
|
1391
|
+
const magenta = '\x1b[35m';
|
|
1392
|
+
const reset = '\x1b[0m';
|
|
1393
|
+
console.log();
|
|
1394
|
+
console.log(` ${gray}[技术设计]${reset} → ${green}[创建提案]${reset} → ${cyan}[实施变更]${reset} → ${yellow}[审查门禁]${reset} → ${magenta}[归档完成]${reset}`);
|
|
1395
|
+
console.log();
|
|
1396
|
+
console.log(` ${gray}/techDesign${reset} ${gray}/proposal${reset} ${cyan}/apply${reset} ${yellow}/review${reset} ${magenta}/archive${reset}`);
|
|
1397
|
+
console.log();
|
|
1398
|
+
console.log(` ${gray}techDesign${reset} : 可选阶段,生成技术设计文档`);
|
|
1399
|
+
console.log(` ${green}propose${reset} : 创建变更提案,等待审批,可集成测试 case`);
|
|
1400
|
+
console.log(` ${cyan}apply${reset} : 实施变更,执行 tasks 追踪进度`);
|
|
1401
|
+
console.log(` ${yellow}review${reset} : 审查门禁,代码审查 + 单测 + spec-code 一致性验证`);
|
|
1402
|
+
console.log(` ${magenta}archive${reset} : 归档完成,更新 specs 记录变更和知识沉淀`);
|
|
1403
|
+
}
|
|
1176
1404
|
formatToolNames(tools) {
|
|
1177
1405
|
const names = tools
|
|
1178
1406
|
.map((tool) => tool.successLabel ?? tool.name)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export function normalizeRequirementName(name) {
|
|
2
2
|
return name.trim();
|
|
3
3
|
}
|
|
4
|
-
const REQUIREMENT_HEADER_REGEX = /^###\s*Requirement
|
|
4
|
+
const REQUIREMENT_HEADER_REGEX = /^###\s*(?:Requirement|需求)[::]\s*(.+)\s*$/i;
|
|
5
|
+
function isRequirementHeader(line) {
|
|
6
|
+
return /^###\s*(?:Requirement|需求)[::]/i.test(line);
|
|
7
|
+
}
|
|
5
8
|
/**
|
|
6
9
|
* Extracts the Requirements section from a spec file and parses requirement blocks.
|
|
7
10
|
*/
|
|
@@ -80,10 +83,10 @@ function normalizeLineEndings(content) {
|
|
|
80
83
|
export function parseDeltaSpec(content) {
|
|
81
84
|
const normalized = normalizeLineEndings(content);
|
|
82
85
|
const sections = splitTopLevelSections(normalized);
|
|
83
|
-
const addedLookup =
|
|
84
|
-
const modifiedLookup =
|
|
85
|
-
const removedLookup =
|
|
86
|
-
const renamedLookup =
|
|
86
|
+
const addedLookup = getSectionByAliases(sections, ['ADDED Requirements', '新增要求']);
|
|
87
|
+
const modifiedLookup = getSectionByAliases(sections, ['MODIFIED Requirements', '修改要求']);
|
|
88
|
+
const removedLookup = getSectionByAliases(sections, ['REMOVED Requirements', '移除要求', '删除要求']);
|
|
89
|
+
const renamedLookup = getSectionByAliases(sections, ['RENAMED Requirements', '重命名要求']);
|
|
87
90
|
const added = parseRequirementBlocksFromSection(addedLookup.body);
|
|
88
91
|
const modified = parseRequirementBlocksFromSection(modifiedLookup.body);
|
|
89
92
|
const removedNames = parseRemovedNames(removedLookup.body);
|
|
@@ -120,10 +123,10 @@ function splitTopLevelSections(content) {
|
|
|
120
123
|
}
|
|
121
124
|
return result;
|
|
122
125
|
}
|
|
123
|
-
function
|
|
124
|
-
const
|
|
126
|
+
function getSectionByAliases(sections, aliases) {
|
|
127
|
+
const targets = new Set(aliases.map((alias) => alias.toLowerCase()));
|
|
125
128
|
for (const [title, body] of Object.entries(sections)) {
|
|
126
|
-
if (title.toLowerCase()
|
|
129
|
+
if (targets.has(title.toLowerCase()))
|
|
127
130
|
return { body, found: true };
|
|
128
131
|
}
|
|
129
132
|
return { body: '', found: false };
|
|
@@ -136,7 +139,7 @@ function parseRequirementBlocksFromSection(sectionBody) {
|
|
|
136
139
|
let i = 0;
|
|
137
140
|
while (i < lines.length) {
|
|
138
141
|
// Seek next requirement header
|
|
139
|
-
while (i < lines.length &&
|
|
142
|
+
while (i < lines.length && !isRequirementHeader(lines[i]))
|
|
140
143
|
i++;
|
|
141
144
|
if (i >= lines.length)
|
|
142
145
|
break;
|
|
@@ -149,7 +152,7 @@ function parseRequirementBlocksFromSection(sectionBody) {
|
|
|
149
152
|
const name = normalizeRequirementName(m[1]);
|
|
150
153
|
const buf = [headerLine];
|
|
151
154
|
i++;
|
|
152
|
-
while (i < lines.length &&
|
|
155
|
+
while (i < lines.length && !isRequirementHeader(lines[i]) && !/^##\s+/.test(lines[i])) {
|
|
153
156
|
buf.push(lines[i]);
|
|
154
157
|
i++;
|
|
155
158
|
}
|