@zhuan-ai/zhuanspec 2.17.1 → 2.17.5

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/dist/cli/index.js CHANGED
@@ -45,6 +45,7 @@ program
45
45
  .description('Initialize ZhuanSpec in your project')
46
46
  .option('--tools <tools>', toolsOptionDescription)
47
47
  .option('--business-direction <direction>', 'Business direction for fetching project architecture (e.g., oms, ass)')
48
+ .option('--domain <domain>', 'Primary domain code; enables domain mode: syncs specs/<domain>/zhuanspec/ and generates project.md via spec-service-architecture skill')
48
49
  .action(async (targetPath = '.', options) => {
49
50
  try {
50
51
  // Validate that the path is a valid directory
@@ -71,6 +72,7 @@ program
71
72
  const initCommand = new InitCommand({
72
73
  tools: options?.tools,
73
74
  businessDirection: options?.businessDirection,
75
+ domain: options?.domain,
74
76
  });
75
77
  await initCommand.execute(targetPath);
76
78
  }
@@ -511,6 +511,17 @@ export class ArchiveCommand {
511
511
  if (explicitDirection && explicitDirection.trim()) {
512
512
  return explicitDirection.trim().toLowerCase();
513
513
  }
514
+ // domain mode: read .domain meta file (takes priority over .business-direction)
515
+ const domainFilePath = path.join(zhuanspecDir, '.domain');
516
+ try {
517
+ const fromDomain = (await fs.readFile(domainFilePath, 'utf-8')).trim();
518
+ if (fromDomain) {
519
+ return fromDomain.toLowerCase();
520
+ }
521
+ }
522
+ catch {
523
+ // Not domain mode, continue.
524
+ }
514
525
  const directionFilePath = path.join(zhuanspecDir, '.business-direction');
515
526
  try {
516
527
  const fromFile = (await fs.readFile(directionFilePath, 'utf-8')).trim();
@@ -29,13 +29,21 @@ type InitCommandOptions = {
29
29
  prompt?: ToolSelectionPrompt;
30
30
  tools?: string;
31
31
  businessDirection?: string;
32
+ domain?: string;
32
33
  };
33
34
  export declare class InitCommand {
34
35
  private readonly prompt;
35
36
  private readonly toolsArg?;
36
37
  private businessDirection?;
38
+ private readonly domain?;
37
39
  constructor(options?: InitCommandOptions);
38
40
  execute(targetPath: string): Promise<void>;
41
+ /**
42
+ * Domain mode initialization: clone remote repo and sync specs/<domain>/zhuanspec/ to local.
43
+ * project.md is generated by the spec-service-architecture skill (injected as post-init instruction).
44
+ */
45
+ private executeDomainSync;
46
+ private generateDomainProjectMdPlaceholder;
39
47
  private validate;
40
48
  private getConfiguration;
41
49
  private getSelectedTools;
package/dist/core/init.js CHANGED
@@ -269,10 +269,12 @@ export class InitCommand {
269
269
  prompt;
270
270
  toolsArg;
271
271
  businessDirection;
272
+ domain;
272
273
  constructor(options = {}) {
273
274
  this.prompt = options.prompt ?? ((config) => toolSelectionWizard(config));
274
275
  this.toolsArg = options.tools;
275
276
  this.businessDirection = options.businessDirection;
277
+ this.domain = options.domain?.trim().toLowerCase() || undefined;
276
278
  }
277
279
  async execute(targetPath) {
278
280
  const projectPath = path.resolve(targetPath);
@@ -308,63 +310,69 @@ export class InitCommand {
308
310
  }
309
311
  // Step 2: Select business direction and sync Claude assets BEFORE configureAITools
310
312
  // This ensures .claude directory is not deleted after slash commands are generated
311
- await this.promptForBusinessDirection();
312
- if (this.businessDirection) {
313
- const directionMetaPath = path.join(zhuanspecPath, '.business-direction');
314
- await FileSystemUtils.writeFile(directionMetaPath, `${this.businessDirection.toLowerCase()}\n`);
315
- const archSpinner = this.startSpinner(`正在获取 ${this.businessDirection} 业务架构文档...`);
316
- const architectureContent = await this.fetchArchitectureFile();
317
- if (architectureContent) {
318
- const projectMdPath = path.join(zhuanspecPath, 'project.md');
319
- const projectMdExists = await FileSystemUtils.fileExists(projectMdPath);
320
- await FileSystemUtils.writeFile(projectMdPath, architectureContent);
321
- archSpinner.stopAndPersist({
322
- symbol: PALETTE.white('▌'),
323
- text: PALETTE.white(projectMdExists
324
- ? `已使用 ${this.businessDirection} 业务架构文档覆盖 project.md`
325
- : `已应用 ${this.businessDirection} 业务架构文档到 project.md`),
326
- });
327
- }
328
- else {
329
- archSpinner.stopAndPersist({
330
- symbol: PALETTE.midGray('▌'),
331
- text: PALETTE.midGray(`未找到 ${this.businessDirection} 业务架构文档,使用默认模板`),
332
- });
333
- }
334
- const specTemplateSpinner = this.startSpinner(`正在同步 ${this.businessDirection} 业务规范模板...`);
335
- const syncResult = this.syncBusinessSpecTemplate(zhuanspecPath);
336
- if (syncResult.syncedSpecs || syncResult.syncedChanges || syncResult.syncedKnowledge) {
337
- const syncedParts = [];
338
- if (syncResult.syncedSpecs) {
339
- syncedParts.push('specs');
313
+ if (this.domain) {
314
+ // Domain mode: skip business direction prompt, sync from specs/<domain>/zhuanspec/
315
+ await this.executeDomainSync(projectPath, zhuanspecPath);
316
+ }
317
+ else {
318
+ await this.promptForBusinessDirection();
319
+ if (this.businessDirection) {
320
+ const directionMetaPath = path.join(zhuanspecPath, '.business-direction');
321
+ await FileSystemUtils.writeFile(directionMetaPath, `${this.businessDirection.toLowerCase()}\n`);
322
+ const archSpinner = this.startSpinner(`正在获取 ${this.businessDirection} 业务架构文档...`);
323
+ const architectureContent = await this.fetchArchitectureFile();
324
+ if (architectureContent) {
325
+ const projectMdPath = path.join(zhuanspecPath, 'project.md');
326
+ const projectMdExists = await FileSystemUtils.fileExists(projectMdPath);
327
+ await FileSystemUtils.writeFile(projectMdPath, architectureContent);
328
+ archSpinner.stopAndPersist({
329
+ symbol: PALETTE.white('▌'),
330
+ text: PALETTE.white(projectMdExists
331
+ ? `已使用 ${this.businessDirection} 业务架构文档覆盖 project.md`
332
+ : `已应用 ${this.businessDirection} 业务架构文档到 project.md`),
333
+ });
340
334
  }
341
- if (syncResult.syncedChanges) {
342
- syncedParts.push('changes');
335
+ else {
336
+ archSpinner.stopAndPersist({
337
+ symbol: PALETTE.midGray('▌'),
338
+ text: PALETTE.midGray(`未找到 ${this.businessDirection} 业务架构文档,使用默认模板`),
339
+ });
343
340
  }
344
- if (syncResult.syncedKnowledge) {
345
- syncedParts.push('knowledge');
341
+ const specTemplateSpinner = this.startSpinner(`正在同步 ${this.businessDirection} 业务规范模板...`);
342
+ const syncResult = this.syncBusinessSpecTemplate(zhuanspecPath);
343
+ if (syncResult.syncedSpecs || syncResult.syncedChanges || syncResult.syncedKnowledge) {
344
+ const syncedParts = [];
345
+ if (syncResult.syncedSpecs) {
346
+ syncedParts.push('specs');
347
+ }
348
+ if (syncResult.syncedChanges) {
349
+ syncedParts.push('changes');
350
+ }
351
+ if (syncResult.syncedKnowledge) {
352
+ syncedParts.push('knowledge');
353
+ }
354
+ specTemplateSpinner.stopAndPersist({
355
+ symbol: PALETTE.white('▌'),
356
+ text: PALETTE.white(`已从远端模板同步 ${syncedParts.join('、')} 目录`),
357
+ });
358
+ }
359
+ else {
360
+ specTemplateSpinner.stopAndPersist({
361
+ symbol: PALETTE.midGray('▌'),
362
+ text: PALETTE.midGray(`未找到 ${this.businessDirection} 业务规范模板目录,保留默认模板`),
363
+ });
346
364
  }
347
- specTemplateSpinner.stopAndPersist({
365
+ // Sync Claude assets BEFORE configureAITools to avoid deletion
366
+ const claudeAssetSpinner = this.startSpinner(`正在同步 ${this.businessDirection} 业务 Claude 规范资产...`);
367
+ const claudeAssetResult = this.syncBusinessClaudeAssets(projectPath);
368
+ const claudeMdText = this.describeClaudeAssetResult('CLAUDE.md', claudeAssetResult.claudeMd);
369
+ const dotClaudeText = this.describeClaudeAssetResult('.claude', claudeAssetResult.dotClaude);
370
+ claudeAssetSpinner.stopAndPersist({
348
371
  symbol: PALETTE.white('▌'),
349
- text: PALETTE.white(`已从远端模板同步 ${syncedParts.join('、')} 目录`),
350
- });
351
- }
352
- else {
353
- specTemplateSpinner.stopAndPersist({
354
- symbol: PALETTE.midGray('▌'),
355
- text: PALETTE.midGray(`未找到 ${this.businessDirection} 业务规范模板目录,保留默认模板`),
372
+ text: PALETTE.white(`Claude 规范资产同步完成:${claudeMdText};${dotClaudeText}`),
356
373
  });
357
374
  }
358
- // Sync Claude assets BEFORE configureAITools to avoid deletion
359
- const claudeAssetSpinner = this.startSpinner(`正在同步 ${this.businessDirection} 业务 Claude 规范资产...`);
360
- const claudeAssetResult = this.syncBusinessClaudeAssets(projectPath);
361
- const claudeMdText = this.describeClaudeAssetResult('CLAUDE.md', claudeAssetResult.claudeMd);
362
- const dotClaudeText = this.describeClaudeAssetResult('.claude', claudeAssetResult.dotClaude);
363
- claudeAssetSpinner.stopAndPersist({
364
- symbol: PALETTE.white('▌'),
365
- text: PALETTE.white(`Claude 规范资产同步完成:${claudeMdText};${dotClaudeText}`),
366
- });
367
- }
375
+ } // end domain else
368
376
  // Step 3: Configure AI tools (after Claude assets sync to preserve generated files)
369
377
  const toolSpinner = this.startSpinner('正在配置 AI 工具...');
370
378
  const toolConfigResult = await this.configureAITools(projectPath, zhuanspecDir, config.aiTools);
@@ -377,6 +385,76 @@ export class InitCommand {
377
385
  // Success message
378
386
  this.displaySuccessMessage(selectedTools, created, refreshed, skippedExisting, skipped, extendMode, toolConfigResult.rootStubStatus, toolConfigResult.claudeHookSummary);
379
387
  }
388
+ /**
389
+ * Domain mode initialization: clone remote repo and sync specs/<domain>/zhuanspec/ to local.
390
+ * project.md is generated by the spec-service-architecture skill (injected as post-init instruction).
391
+ */
392
+ async executeDomainSync(projectPath, zhuanspecPath) {
393
+ const domain = this.domain;
394
+ // Save domain code to .domain meta file (used by archive for push routing)
395
+ const domainMetaPath = path.join(zhuanspecPath, '.domain');
396
+ await FileSystemUtils.writeFile(domainMetaPath, `${domain}\n`);
397
+ const syncSpinner = this.startSpinner(`正在从远端同步 ${domain} 主领域模板(specs/${domain}/zhuanspec/)...`);
398
+ const tempDir = this.cloneRemoteArchitectureRepoToTemp();
399
+ if (!tempDir) {
400
+ syncSpinner.stopAndPersist({
401
+ symbol: PALETTE.midGray('▌'),
402
+ text: PALETTE.midGray(`克隆远端仓库失败,跳过 ${domain} 主领域模板同步`),
403
+ });
404
+ }
405
+ else {
406
+ try {
407
+ const remoteZhuanspecDir = path.join(tempDir, 'specs', domain, 'zhuanspec');
408
+ if (!existsSync(remoteZhuanspecDir)) {
409
+ syncSpinner.stopAndPersist({
410
+ symbol: PALETTE.midGray('▌'),
411
+ text: PALETTE.midGray(`远端未找到 specs/${domain}/zhuanspec/ 目录,跳过模板同步`),
412
+ });
413
+ }
414
+ else {
415
+ // Sync all contents from specs/<domain>/zhuanspec/ into local zhuanspec/
416
+ this.copyDirectoryContentsIfExists(remoteZhuanspecDir, zhuanspecPath);
417
+ syncSpinner.stopAndPersist({
418
+ symbol: PALETTE.white('▌'),
419
+ text: PALETTE.white(`已从远端 specs/${domain}/zhuanspec/ 同步模板到本地 zhuanspec/`),
420
+ });
421
+ }
422
+ }
423
+ finally {
424
+ rmSync(tempDir, { recursive: true, force: true });
425
+ }
426
+ }
427
+ // project.md is generated by spec-service-architecture skill after init
428
+ // Write a placeholder so Claude knows to invoke the skill
429
+ const projectMdPath = path.join(zhuanspecPath, 'project.md');
430
+ if (!(await FileSystemUtils.fileExists(projectMdPath))) {
431
+ await this.generateDomainProjectMdPlaceholder(projectMdPath, domain);
432
+ }
433
+ // Sync Claude assets from common
434
+ const claudeAssetSpinner = this.startSpinner('正在同步 Claude 规范资产(common)...');
435
+ const claudeAssetResult = this.syncBusinessClaudeAssets(projectPath);
436
+ const claudeMdText = this.describeClaudeAssetResult('CLAUDE.md', claudeAssetResult.claudeMd);
437
+ const dotClaudeText = this.describeClaudeAssetResult('.claude', claudeAssetResult.dotClaude);
438
+ claudeAssetSpinner.stopAndPersist({
439
+ symbol: PALETTE.white('▌'),
440
+ text: PALETTE.white(`Claude 规范资产同步完成:${claudeMdText};${dotClaudeText}`),
441
+ });
442
+ // Remind user to run spec-service-architecture skill to generate project.md
443
+ console.log();
444
+ console.log(PALETTE.white('domain 模式提示'));
445
+ console.log(PALETTE.lightGray(` project.md 需由 @spec-service-architecture skill 生成,请在 AI 工具中执行:`));
446
+ console.log(PALETTE.midGray(` /spec-service-architecture`));
447
+ console.log(PALETTE.midGray(` 生成完成后,文件将保存为 zhuanspec/project.md`));
448
+ }
449
+ async generateDomainProjectMdPlaceholder(projectMdPath, domain) {
450
+ const placeholder = `# Project Architecture
451
+
452
+ <!-- 此文件由 domain 模式初始化,需通过 @spec-service-architecture skill 生成完整内容 -->
453
+ <!-- domain: ${domain} -->
454
+ <!-- 请在 AI 工具中执行 /spec-service-architecture,生成结果将覆盖此文件 -->
455
+ `;
456
+ await FileSystemUtils.writeFile(projectMdPath, placeholder);
457
+ }
380
458
  async validate(projectPath, _zhuanspecPath) {
381
459
  const extendMode = await FileSystemUtils.directoryExists(_zhuanspecPath);
382
460
  // Check write permissions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhuan-ai/zhuanspec",
3
- "version": "2.17.1",
3
+ "version": "2.17.5",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "zhuanspec",