@zhuan-ai/zhuanspec 2.11.4 → 2.11.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cli/hooks.js +3 -2
  2. package/dist/commands/change.js +9 -4
  3. package/dist/commands/progress.js +7 -6
  4. package/dist/commands/spec.js +12 -4
  5. package/dist/commands/validate.js +15 -6
  6. package/dist/core/artifact-graph/instruction-loader.d.ts +3 -1
  7. package/dist/core/artifact-graph/instruction-loader.js +27 -2
  8. package/dist/core/business-assets-sync.d.ts +94 -0
  9. package/dist/core/business-assets-sync.js +562 -0
  10. package/dist/core/hooks/collect-knowledge.js +4 -1
  11. package/dist/core/hooks/deviation-check.js +22 -5
  12. package/dist/core/hooks/init.js +18 -2
  13. package/dist/core/hooks/pre-review.js +18 -5
  14. package/dist/core/hooks/record-progress.d.ts +12 -0
  15. package/dist/core/hooks/record-progress.js +53 -7
  16. package/dist/core/init.js +1 -1
  17. package/dist/core/task-graph/execution-planner.d.ts +17 -2
  18. package/dist/core/task-graph/execution-planner.js +33 -2
  19. package/dist/core/task-graph/integration-report-template.d.ts +30 -0
  20. package/dist/core/task-graph/integration-report-template.js +210 -0
  21. package/dist/core/task-graph/types.d.ts +73 -0
  22. package/dist/core/task-graph/xml-renderer.js +51 -6
  23. package/dist/core/templates/agents-root-stub.d.ts +1 -1
  24. package/dist/core/templates/agents-root-stub.js +2 -0
  25. package/dist/core/templates/agents-template.d.ts +1 -1
  26. package/dist/core/templates/agents-template.js +70 -18
  27. package/dist/core/templates/slash-command-templates.js +243 -68
  28. package/dist/core/templates/tasks-template.d.ts +0 -7
  29. package/dist/core/templates/tasks-template.js +7 -45
  30. package/dist/core/templates/tdd-tasks-template.d.ts +0 -6
  31. package/dist/core/templates/tdd-tasks-template.js +157 -75
  32. package/dist/core/templates/test-cases-template.d.ts +7 -0
  33. package/dist/core/templates/test-cases-template.js +42 -35
  34. package/dist/core/update.d.ts +2 -3
  35. package/dist/core/update.js +146 -66
  36. package/dist/core/validation/strict-rules.d.ts +10 -9
  37. package/dist/core/validation/strict-rules.js +180 -81
  38. package/dist/core/validation/validator.js +21 -0
  39. package/dist/utils/item-discovery.js +4 -3
  40. package/dist/utils/resolve-root.d.ts +26 -0
  41. package/dist/utils/resolve-root.js +80 -0
  42. package/dist/utils/task-progress.js +2 -2
  43. package/package.json +1 -1
  44. package/schemas/spec-driven/schema.yaml +10 -2
  45. package/schemas/spec-driven/templates/tasks.md +117 -4
package/dist/cli/hooks.js CHANGED
@@ -23,6 +23,7 @@ import { postApplyHook, executePostApply } from '../core/hooks/post-apply.js';
23
23
  import { codeReviewResultCheck, unitTestResultCheck, specConsistencyResultCheck, generateReviewReport, } from '../core/hooks/review-hooks.js';
24
24
  import path from 'path';
25
25
  import { FileSystemUtils } from '../utils/file-system.js';
26
+ import { resolveZhuanSpecRoot } from '../utils/resolve-root.js';
26
27
  const program = new Command();
27
28
  program
28
29
  .name('zhuanspec-hook')
@@ -295,7 +296,7 @@ program
295
296
  * Phase Transition Check - Unified function for phase transition validation
296
297
  */
297
298
  async function runPhaseTransitionCheck(options) {
298
- const cwd = process.cwd();
299
+ const cwd = resolveZhuanSpecRoot();
299
300
  const changeId = options.change || process.env.ZHUANSPEC_CHANGE_ID || '';
300
301
  const fromPhase = options.from || process.env.ZHUANSPEC_PHASE || 'idle';
301
302
  const toPhase = options.to || '';
@@ -394,7 +395,7 @@ async function runPhaseTransitionCheck(options) {
394
395
  }
395
396
  }
396
397
  async function collectHookStatus() {
397
- const cwd = process.cwd();
398
+ const cwd = resolveZhuanSpecRoot();
398
399
  const settingsPath = path.join(cwd, '.claude', 'settings.json');
399
400
  const settingsExists = await FileSystemUtils.fileExists(settingsPath);
400
401
  const registeredLifecycleHooks = [];
@@ -1,5 +1,6 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import path from 'path';
3
+ import chalk from 'chalk';
3
4
  import { JsonConverter } from '../core/converters/json-converter.js';
4
5
  import { Validator } from '../core/validation/validator.js';
5
6
  import { ChangeParser } from '../core/parsers/change-parser.js';
@@ -211,11 +212,15 @@ export class ChangeCommand {
211
212
  console.log(`Change "${changeName}" is valid`);
212
213
  }
213
214
  else {
214
- console.error(`Change "${changeName}" has issues`);
215
+ console.error(`变更 "${changeName}" 存在问题`);
215
216
  report.issues.forEach(issue => {
216
- const label = issue.level === 'ERROR' ? 'ERROR' : 'WARNING';
217
- const prefix = issue.level === 'ERROR' ? '✗' : '⚠';
218
- console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
217
+ const label = issue.level === 'ERROR' ? '错误' : '警告';
218
+ if (issue.level === 'ERROR') {
219
+ console.error(chalk.red(`✗ [${label}] ${issue.path}: ${issue.message}`));
220
+ }
221
+ else {
222
+ console.error(chalk.yellow(`⚠ [${label}] ${issue.path}: ${issue.message}`));
223
+ }
219
224
  });
220
225
  // Next steps footer to guide fixing issues
221
226
  this.printNextSteps();
@@ -11,6 +11,7 @@ import { promises as fs } from 'fs';
11
11
  import { FileSystemUtils } from '../utils/file-system.js';
12
12
  import { PHASE_ORDER } from '../utils/phase-utils.js';
13
13
  import { initializeProgress } from '../core/hooks/record-progress.js';
14
+ import { resolveZhuanSpecRoot } from '../utils/resolve-root.js';
14
15
  export class ProgressCommand {
15
16
  /**
16
17
  * Recover damaged progress.json using three-tier fallback strategy
@@ -80,7 +81,7 @@ export class ProgressCommand {
80
81
  * Usage: zhuanspec progress show <change-id>
81
82
  */
82
83
  async show(changeId, options) {
83
- const cwd = process.cwd();
84
+ const cwd = resolveZhuanSpecRoot();
84
85
  const zhuanspecDir = path.join(cwd, 'zhuanspec');
85
86
  const changeDir = path.join(zhuanspecDir, 'changes', changeId);
86
87
  const metricsDir = path.join(changeDir, 'metrics');
@@ -110,7 +111,7 @@ export class ProgressCommand {
110
111
  }
111
112
  }
112
113
  async execute(options) {
113
- const cwd = process.cwd();
114
+ const cwd = resolveZhuanSpecRoot();
114
115
  const zhuanspecDir = path.join(cwd, 'zhuanspec');
115
116
  // Check if zhuanspec directory exists
116
117
  if (!await FileSystemUtils.fileExists(zhuanspecDir)) {
@@ -211,12 +212,12 @@ export class ProgressCommand {
211
212
  });
212
213
  continue;
213
214
  }
214
- // Match "Task X.Y" format: #### Task 1.0: description or #### Task 1.1: description
215
- const taskXYMatch = line.match(/^####\s*Task\s+(\d+\.\d+)\s*:\s*(.+)$/i);
215
+ // Match "Task X.Y" or bare "X.Y" format: #### Task 1.0: description or #### 1.1 description
216
+ const taskXYMatch = line.match(/^####\s*(?:Task\s+)?(\d+\.\d+)[\s:]\s*(.+)$/i);
216
217
  if (taskXYMatch) {
217
218
  const taskId = taskXYMatch[1];
218
219
  const rawDescription = taskXYMatch[2].trim();
219
- const isCompleted = rawDescription.endsWith('');
220
+ const isCompleted = rawDescription.endsWith('\u2705');
220
221
  const description = isCompleted
221
222
  ? rawDescription.slice(0, -1).trim()
222
223
  : rawDescription;
@@ -351,7 +352,7 @@ export class ProgressCommand {
351
352
  * Usage: zhuanspec progress set-phase <change-id> <phase>
352
353
  */
353
354
  async setPhase(changeId, phase) {
354
- const cwd = process.cwd();
355
+ const cwd = resolveZhuanSpecRoot();
355
356
  const changeDir = path.join(cwd, 'zhuanspec', 'changes', changeId);
356
357
  if (!await FileSystemUtils.directoryExists(changeDir)) {
357
358
  throw new Error(`Change '${changeId}' not found`);
@@ -1,3 +1,4 @@
1
+ import chalk from 'chalk';
1
2
  import { existsSync, readdirSync, readFileSync } from 'fs';
2
3
  import { join } from 'path';
3
4
  import { MarkdownParser } from '../core/parsers/markdown-parser.js';
@@ -205,11 +206,18 @@ export function registerSpecCommand(rootProgram) {
205
206
  console.log(`Specification '${specId}' is valid`);
206
207
  }
207
208
  else {
208
- console.error(`Specification '${specId}' has issues`);
209
+ console.error(`规范 '${specId}' 存在问题`);
209
210
  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}`);
211
+ const label = issue.level === 'ERROR' ? '错误' : issue.level === 'WARNING' ? '警告' : '提示';
212
+ if (issue.level === 'ERROR') {
213
+ console.error(chalk.red(`✗ [${label}] ${issue.path}: ${issue.message}`));
214
+ }
215
+ else if (issue.level === 'WARNING') {
216
+ console.error(chalk.yellow(`⚠ [${label}] ${issue.path}: ${issue.message}`));
217
+ }
218
+ else {
219
+ console.error(`ℹ [${label}] ${issue.path}: ${issue.message}`);
220
+ }
213
221
  });
214
222
  }
215
223
  }
@@ -292,18 +292,25 @@ export class ValidateCommand {
292
292
  }
293
293
  }
294
294
  else {
295
- console.error(`${type === 'change' ? 'Change' : 'Specification'} '${id}' has issues`);
295
+ console.error(`${type === 'change' ? '变更' : '规范'} '${id}' 存在问题`);
296
296
  for (const issue of report.issues) {
297
- const label = issue.level === 'ERROR' ? 'ERROR' : issue.level;
298
- const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
299
- console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
297
+ const label = issue.level === 'ERROR' ? '错误' : issue.level === 'WARNING' ? '警告' : '提示';
298
+ if (issue.level === 'ERROR') {
299
+ console.error(chalk.red(`✗ [${label}] ${issue.path}: ${issue.message}`));
300
+ }
301
+ else if (issue.level === 'WARNING') {
302
+ console.error(chalk.yellow(`⚠ [${label}] ${issue.path}: ${issue.message}`));
303
+ }
304
+ else {
305
+ console.error(`ℹ [${label}] ${issue.path}: ${issue.message}`);
306
+ }
300
307
  }
301
308
  this.printNextSteps(type);
302
309
  }
303
310
  // Print strict mode checks summary if available
304
311
  if (opts?.strict && report.strictChecks && report.strictChecks.length > 0) {
305
312
  console.log('');
306
- console.log('Strict Mode Checks:');
313
+ console.log('严格模式检查:');
307
314
  for (const check of report.strictChecks) {
308
315
  if (check.passed) {
309
316
  console.log(` ${chalk.green('✓')} ${check.ruleName}`);
@@ -311,7 +318,9 @@ export class ValidateCommand {
311
318
  else {
312
319
  const firstError = check.errors[0] || check.warnings[0] || '';
313
320
  const errorSuffix = firstError ? ` — ${firstError}` : '';
314
- console.log(` ${chalk.red('✗')} ${check.ruleName}${errorSuffix}`);
321
+ const hasErrors = check.errors.length > 0;
322
+ const color = hasErrors ? chalk.red : chalk.yellow;
323
+ console.log(` ${color('✗')} ${check.ruleName}${errorSuffix}`);
315
324
  }
316
325
  }
317
326
  }
@@ -100,7 +100,9 @@ export interface ChangeStatus {
100
100
  * @returns The template content
101
101
  * @throws TemplateLoadError if the template cannot be loaded
102
102
  */
103
- export declare function loadTemplate(schemaName: string, templatePath: string): string;
103
+ export declare function loadTemplate(schemaName: string, templatePath: string, options?: {
104
+ changeName?: string;
105
+ }): string;
104
106
  /**
105
107
  * Loads change context combining graph and completion state.
106
108
  *
@@ -5,6 +5,8 @@ import { ArtifactGraph } from './graph.js';
5
5
  import { detectCompleted } from './state.js';
6
6
  import { resolveSchemaForChange } from '../../utils/change-metadata.js';
7
7
  import { loadProjectConfig, getEffectiveRules } from '../project-config.js';
8
+ import { getTasksTemplate } from '../templates/tasks-template.js';
9
+ import { getTddTasksTemplate } from '../templates/tdd-tasks-template.js';
8
10
  /**
9
11
  * Error thrown when loading a template fails.
10
12
  */
@@ -24,7 +26,13 @@ export class TemplateLoadError extends Error {
24
26
  * @returns The template content
25
27
  * @throws TemplateLoadError if the template cannot be loaded
26
28
  */
27
- export function loadTemplate(schemaName, templatePath) {
29
+ export function loadTemplate(schemaName, templatePath, options) {
30
+ // Prefer TypeScript template functions as single source of truth
31
+ const tsTemplate = loadFromTypeScriptTemplate(schemaName, templatePath, options);
32
+ if (tsTemplate !== null) {
33
+ return tsTemplate;
34
+ }
35
+ // Fallback: load from static file
28
36
  const schemaDir = getSchemaDir(schemaName);
29
37
  if (!schemaDir) {
30
38
  throw new TemplateLoadError(`Schema '${schemaName}' not found`, templatePath);
@@ -41,6 +49,23 @@ export function loadTemplate(schemaName, templatePath) {
41
49
  throw new TemplateLoadError(`Failed to read template: ${ioError.message}`, fullPath);
42
50
  }
43
51
  }
52
+ /**
53
+ * Attempt to load template from TypeScript template functions.
54
+ * Returns null if no TypeScript template is registered for this schema+template combination.
55
+ * This ensures TypeScript templates are the single source of truth,
56
+ * avoiding dual-track desync between .ts functions and static .md files.
57
+ */
58
+ function loadFromTypeScriptTemplate(schemaName, templatePath, options) {
59
+ if (templatePath === 'tasks.md') {
60
+ if (schemaName === 'spec-driven') {
61
+ return getTasksTemplate({ changeId: options?.changeName || '[change-id]' });
62
+ }
63
+ if (schemaName === 'tdd') {
64
+ return getTddTasksTemplate({ changeId: options?.changeName || '[change-id]' });
65
+ }
66
+ }
67
+ return null;
68
+ }
44
69
  /**
45
70
  * Loads change context combining graph and completion state.
46
71
  *
@@ -82,7 +107,7 @@ export function generateInstructions(context, artifactId) {
82
107
  if (!artifact) {
83
108
  throw new Error(`Artifact '${artifactId}' not found in schema '${context.schemaName}'`);
84
109
  }
85
- const template = loadTemplate(context.schemaName, artifact.template);
110
+ const template = loadTemplate(context.schemaName, artifact.template, { changeName: context.changeName });
86
111
  const dependencies = getDependencyInfo(artifact, context.graph, context.completed);
87
112
  const unlocks = getUnlockedArtifacts(context.graph, artifactId);
88
113
  // Load project config for context/rules injection
@@ -0,0 +1,94 @@
1
+ export declare const ARCH_REPO_URL = "http://gitlab.zhuanspirit.com/zz-kf/spec_repo.git";
2
+ export declare const ARCH_REPO_BRANCH = "spec_repo-feature-6612-2";
3
+ export type ClaudeAssetSource = 'business' | 'common' | 'none';
4
+ export type FileSyncStatus = 'copied' | 'overwritten' | 'skipped-unchanged';
5
+ export type ClaudeAssetFileSyncResult = {
6
+ source: ClaudeAssetSource;
7
+ files: {
8
+ added: string[];
9
+ overwritten: string[];
10
+ skipped: string[];
11
+ };
12
+ };
13
+ export type ClaudeAssetSyncResult = {
14
+ claudeMd: {
15
+ source: ClaudeAssetSource;
16
+ status: FileSyncStatus | 'missing';
17
+ };
18
+ dotClaude: ClaudeAssetFileSyncResult;
19
+ };
20
+ export type SpecTemplateSyncDetail = {
21
+ synced: boolean;
22
+ added: number;
23
+ skipped: number;
24
+ };
25
+ export type SpecTemplateSyncResult = {
26
+ syncedSpecs: boolean;
27
+ syncedChanges: boolean;
28
+ syncedKnowledge: boolean;
29
+ specs: SpecTemplateSyncDetail;
30
+ changes: SpecTemplateSyncDetail;
31
+ knowledge: SpecTemplateSyncDetail;
32
+ };
33
+ export declare function cloneArchRepoToTemp(prefix?: string): string | null;
34
+ export declare function readBusinessDirection(zhuanspecPath: string): string | null;
35
+ export declare function resolveBusinessDirectionRoot(specsRoot: string, businessDirection: string | null): string | null;
36
+ export declare function findFirstDirectoryByName(searchRoot: string, targetName: string): string | null;
37
+ export declare function findFirstTemplateSourceRoot(searchRoot: string): string | null;
38
+ export declare function copyDirectoryContentsIfExists(sourceDir: string, targetDir: string): boolean;
39
+ /**
40
+ * Recursively copy src to dst.
41
+ * If the target file already exists, it is left untouched (skipped).
42
+ * This protects user's local modifications, used by `zhuanspec update`.
43
+ */
44
+ export declare function mergeDirectoryAdditive(sourceDir: string, targetDir: string): {
45
+ exists: boolean;
46
+ added: number;
47
+ skipped: number;
48
+ };
49
+ export declare function resolveClaudeAssetSource(businessPath: string | null, commonPath: string): {
50
+ source: Exclude<ClaudeAssetSource, 'none'>;
51
+ path: string;
52
+ } | null;
53
+ export declare function copyClaudeFileIfNeeded(sourceAsset: {
54
+ source: Exclude<ClaudeAssetSource, 'none'>;
55
+ path: string;
56
+ } | null, targetPath: string): {
57
+ source: ClaudeAssetSource;
58
+ status: FileSyncStatus | 'missing';
59
+ };
60
+ export declare function copyClaudeDirectoryIfNeeded(sourceAsset: {
61
+ source: Exclude<ClaudeAssetSource, 'none'>;
62
+ path: string;
63
+ } | null, targetPath: string): ClaudeAssetFileSyncResult;
64
+ export declare function copyClaudeDirectoryFromCommon(commonSourcePath: string, targetPath: string): ClaudeAssetFileSyncResult;
65
+ export declare function fetchRemoteArchitectureFiles(existingTempDir?: string | null): string[];
66
+ export declare function fetchRemoteArchitectureContent(fileName: string, existingTempDir?: string | null): string | null;
67
+ /**
68
+ * Fetch the business architecture file content.
69
+ *
70
+ * Priority:
71
+ * 1. remote clone (via `existingTempDir` if provided, otherwise fresh clone)
72
+ * 2. `git show` from local repo at `repoCwd` (skipped when repoCwd is null)
73
+ * 3. direct file read from `repoCwd` (skipped when repoCwd is null)
74
+ */
75
+ export declare function fetchArchitectureFile(businessDirection: string | null, repoCwd: string | null, existingTempDir?: string | null): Promise<string | null>;
76
+ /**
77
+ * Sync business-scoped template directories (specs / changes / knowledge).
78
+ * `mode='overwrite'` preserves legacy init behavior (cpSync force:true).
79
+ * `mode='additive'` only creates missing files (used by `zhuanspec update`).
80
+ */
81
+ export declare function syncBusinessSpecTemplate(zhuanspecPath: string, businessDirection: string | null, mode?: 'overwrite' | 'additive', existingTempDir?: string | null): SpecTemplateSyncResult;
82
+ export declare function syncBusinessClaudeAssets(projectPath: string, businessDirection: string | null, existingTempDir?: string | null): ClaudeAssetSyncResult;
83
+ /**
84
+ * Sync `.claude/` from the common directory only.
85
+ * Used by `zhuanspec update` when no `.business-direction` is configured.
86
+ */
87
+ export declare function syncCommonDotClaude(projectPath: string, existingTempDir?: string | null): {
88
+ attempted: boolean;
89
+ synced: boolean;
90
+ added: number;
91
+ overwritten: number;
92
+ skipped: number;
93
+ };
94
+ //# sourceMappingURL=business-assets-sync.d.ts.map