@zhuan-ai/zhuanspec 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +461 -0
  3. package/README.zh.md +434 -0
  4. package/bin/zhuanspec.js +3 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.js +356 -0
  7. package/dist/commands/artifact-workflow.d.ts +13 -0
  8. package/dist/commands/artifact-workflow.js +916 -0
  9. package/dist/commands/change.d.ts +35 -0
  10. package/dist/commands/change.js +277 -0
  11. package/dist/commands/completion.d.ts +72 -0
  12. package/dist/commands/completion.js +221 -0
  13. package/dist/commands/config.d.ts +8 -0
  14. package/dist/commands/config.js +198 -0
  15. package/dist/commands/show.d.ts +14 -0
  16. package/dist/commands/show.js +132 -0
  17. package/dist/commands/spec.d.ts +15 -0
  18. package/dist/commands/spec.js +225 -0
  19. package/dist/commands/validate.d.ts +24 -0
  20. package/dist/commands/validate.js +294 -0
  21. package/dist/core/archive.d.ts +30 -0
  22. package/dist/core/archive.js +438 -0
  23. package/dist/core/artifact-graph/graph.d.ts +56 -0
  24. package/dist/core/artifact-graph/graph.js +141 -0
  25. package/dist/core/artifact-graph/index.d.ts +7 -0
  26. package/dist/core/artifact-graph/index.js +13 -0
  27. package/dist/core/artifact-graph/instruction-loader.d.ts +134 -0
  28. package/dist/core/artifact-graph/instruction-loader.js +180 -0
  29. package/dist/core/artifact-graph/resolver.d.ts +61 -0
  30. package/dist/core/artifact-graph/resolver.js +187 -0
  31. package/dist/core/artifact-graph/schema.d.ts +13 -0
  32. package/dist/core/artifact-graph/schema.js +108 -0
  33. package/dist/core/artifact-graph/state.d.ts +12 -0
  34. package/dist/core/artifact-graph/state.js +54 -0
  35. package/dist/core/artifact-graph/types.d.ts +45 -0
  36. package/dist/core/artifact-graph/types.js +43 -0
  37. package/dist/core/completions/command-registry.d.ts +7 -0
  38. package/dist/core/completions/command-registry.js +362 -0
  39. package/dist/core/completions/completion-provider.d.ts +60 -0
  40. package/dist/core/completions/completion-provider.js +102 -0
  41. package/dist/core/completions/factory.d.ts +51 -0
  42. package/dist/core/completions/factory.js +57 -0
  43. package/dist/core/completions/generators/zsh-generator.d.ts +58 -0
  44. package/dist/core/completions/generators/zsh-generator.js +319 -0
  45. package/dist/core/completions/installers/zsh-installer.d.ts +136 -0
  46. package/dist/core/completions/installers/zsh-installer.js +449 -0
  47. package/dist/core/completions/types.d.ts +78 -0
  48. package/dist/core/completions/types.js +2 -0
  49. package/dist/core/config-schema.d.ts +76 -0
  50. package/dist/core/config-schema.js +200 -0
  51. package/dist/core/config.d.ts +16 -0
  52. package/dist/core/config.js +29 -0
  53. package/dist/core/configurators/agents.d.ts +8 -0
  54. package/dist/core/configurators/agents.js +15 -0
  55. package/dist/core/configurators/base.d.ts +7 -0
  56. package/dist/core/configurators/base.js +2 -0
  57. package/dist/core/configurators/claude.d.ts +8 -0
  58. package/dist/core/configurators/claude.js +15 -0
  59. package/dist/core/configurators/cline.d.ts +8 -0
  60. package/dist/core/configurators/cline.js +15 -0
  61. package/dist/core/configurators/codebuddy.d.ts +8 -0
  62. package/dist/core/configurators/codebuddy.js +15 -0
  63. package/dist/core/configurators/costrict.d.ts +8 -0
  64. package/dist/core/configurators/costrict.js +15 -0
  65. package/dist/core/configurators/iflow.d.ts +8 -0
  66. package/dist/core/configurators/iflow.js +15 -0
  67. package/dist/core/configurators/qoder.d.ts +30 -0
  68. package/dist/core/configurators/qoder.js +42 -0
  69. package/dist/core/configurators/qwen.d.ts +24 -0
  70. package/dist/core/configurators/qwen.js +37 -0
  71. package/dist/core/configurators/registry.d.ts +9 -0
  72. package/dist/core/configurators/registry.js +43 -0
  73. package/dist/core/configurators/slash/amazon-q.d.ts +9 -0
  74. package/dist/core/configurators/slash/amazon-q.js +46 -0
  75. package/dist/core/configurators/slash/antigravity.d.ts +9 -0
  76. package/dist/core/configurators/slash/antigravity.js +23 -0
  77. package/dist/core/configurators/slash/auggie.d.ts +9 -0
  78. package/dist/core/configurators/slash/auggie.js +31 -0
  79. package/dist/core/configurators/slash/base.d.ts +19 -0
  80. package/dist/core/configurators/slash/base.js +69 -0
  81. package/dist/core/configurators/slash/claude.d.ts +9 -0
  82. package/dist/core/configurators/slash/claude.js +37 -0
  83. package/dist/core/configurators/slash/cline.d.ts +9 -0
  84. package/dist/core/configurators/slash/cline.js +23 -0
  85. package/dist/core/configurators/slash/codebuddy.d.ts +9 -0
  86. package/dist/core/configurators/slash/codebuddy.js +37 -0
  87. package/dist/core/configurators/slash/codex.d.ts +14 -0
  88. package/dist/core/configurators/slash/codex.js +109 -0
  89. package/dist/core/configurators/slash/costrict.d.ts +9 -0
  90. package/dist/core/configurators/slash/costrict.js +31 -0
  91. package/dist/core/configurators/slash/crush.d.ts +9 -0
  92. package/dist/core/configurators/slash/crush.js +37 -0
  93. package/dist/core/configurators/slash/cursor.d.ts +9 -0
  94. package/dist/core/configurators/slash/cursor.js +37 -0
  95. package/dist/core/configurators/slash/factory.d.ts +10 -0
  96. package/dist/core/configurators/slash/factory.js +35 -0
  97. package/dist/core/configurators/slash/gemini.d.ts +9 -0
  98. package/dist/core/configurators/slash/gemini.js +22 -0
  99. package/dist/core/configurators/slash/github-copilot.d.ts +9 -0
  100. package/dist/core/configurators/slash/github-copilot.js +34 -0
  101. package/dist/core/configurators/slash/iflow.d.ts +9 -0
  102. package/dist/core/configurators/slash/iflow.js +37 -0
  103. package/dist/core/configurators/slash/kilocode.d.ts +9 -0
  104. package/dist/core/configurators/slash/kilocode.js +17 -0
  105. package/dist/core/configurators/slash/opencode.d.ts +12 -0
  106. package/dist/core/configurators/slash/opencode.js +72 -0
  107. package/dist/core/configurators/slash/qoder.d.ts +35 -0
  108. package/dist/core/configurators/slash/qoder.js +76 -0
  109. package/dist/core/configurators/slash/qwen.d.ts +32 -0
  110. package/dist/core/configurators/slash/qwen.js +49 -0
  111. package/dist/core/configurators/slash/registry.d.ts +8 -0
  112. package/dist/core/configurators/slash/registry.js +75 -0
  113. package/dist/core/configurators/slash/roocode.d.ts +9 -0
  114. package/dist/core/configurators/slash/roocode.js +23 -0
  115. package/dist/core/configurators/slash/toml-base.d.ts +10 -0
  116. package/dist/core/configurators/slash/toml-base.js +53 -0
  117. package/dist/core/configurators/slash/windsurf.d.ts +9 -0
  118. package/dist/core/configurators/slash/windsurf.js +23 -0
  119. package/dist/core/converters/json-converter.d.ts +6 -0
  120. package/dist/core/converters/json-converter.js +51 -0
  121. package/dist/core/global-config.d.ts +39 -0
  122. package/dist/core/global-config.js +115 -0
  123. package/dist/core/index.d.ts +2 -0
  124. package/dist/core/index.js +3 -0
  125. package/dist/core/init.d.ts +60 -0
  126. package/dist/core/init.js +861 -0
  127. package/dist/core/list.d.ts +9 -0
  128. package/dist/core/list.js +171 -0
  129. package/dist/core/parsers/change-parser.d.ts +13 -0
  130. package/dist/core/parsers/change-parser.js +193 -0
  131. package/dist/core/parsers/markdown-parser.d.ts +22 -0
  132. package/dist/core/parsers/markdown-parser.js +187 -0
  133. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  134. package/dist/core/parsers/requirement-blocks.js +201 -0
  135. package/dist/core/project-config.d.ts +34 -0
  136. package/dist/core/project-config.js +79 -0
  137. package/dist/core/schemas/base.schema.d.ts +13 -0
  138. package/dist/core/schemas/base.schema.js +13 -0
  139. package/dist/core/schemas/change.schema.d.ts +73 -0
  140. package/dist/core/schemas/change.schema.js +31 -0
  141. package/dist/core/schemas/index.d.ts +4 -0
  142. package/dist/core/schemas/index.js +4 -0
  143. package/dist/core/schemas/spec.schema.d.ts +18 -0
  144. package/dist/core/schemas/spec.schema.js +15 -0
  145. package/dist/core/skill-discovery.d.ts +24 -0
  146. package/dist/core/skill-discovery.js +153 -0
  147. package/dist/core/specs-apply.d.ts +73 -0
  148. package/dist/core/specs-apply.js +384 -0
  149. package/dist/core/styles/palette.d.ts +7 -0
  150. package/dist/core/styles/palette.js +8 -0
  151. package/dist/core/templates/agents-root-stub.d.ts +2 -0
  152. package/dist/core/templates/agents-root-stub.js +17 -0
  153. package/dist/core/templates/agents-template.d.ts +2 -0
  154. package/dist/core/templates/agents-template.js +706 -0
  155. package/dist/core/templates/claude-template.d.ts +2 -0
  156. package/dist/core/templates/claude-template.js +2 -0
  157. package/dist/core/templates/cline-template.d.ts +2 -0
  158. package/dist/core/templates/cline-template.js +2 -0
  159. package/dist/core/templates/costrict-template.d.ts +2 -0
  160. package/dist/core/templates/costrict-template.js +2 -0
  161. package/dist/core/templates/index.d.ts +17 -0
  162. package/dist/core/templates/index.js +37 -0
  163. package/dist/core/templates/project-template.d.ts +8 -0
  164. package/dist/core/templates/project-template.js +32 -0
  165. package/dist/core/templates/skill-templates.d.ts +103 -0
  166. package/dist/core/templates/skill-templates.js +2131 -0
  167. package/dist/core/templates/slash-command-templates.d.ts +4 -0
  168. package/dist/core/templates/slash-command-templates.js +81 -0
  169. package/dist/core/update.d.ts +4 -0
  170. package/dist/core/update.js +88 -0
  171. package/dist/core/validation/constants.d.ts +34 -0
  172. package/dist/core/validation/constants.js +40 -0
  173. package/dist/core/validation/types.d.ts +18 -0
  174. package/dist/core/validation/types.js +2 -0
  175. package/dist/core/validation/validator.d.ts +33 -0
  176. package/dist/core/validation/validator.js +409 -0
  177. package/dist/core/view.d.ts +8 -0
  178. package/dist/core/view.js +168 -0
  179. package/dist/index.d.ts +3 -0
  180. package/dist/index.js +3 -0
  181. package/dist/utils/change-metadata.d.ts +47 -0
  182. package/dist/utils/change-metadata.js +130 -0
  183. package/dist/utils/change-utils.d.ts +51 -0
  184. package/dist/utils/change-utils.js +100 -0
  185. package/dist/utils/file-system.d.ts +19 -0
  186. package/dist/utils/file-system.js +177 -0
  187. package/dist/utils/index.d.ts +4 -0
  188. package/dist/utils/index.js +5 -0
  189. package/dist/utils/interactive.d.ts +18 -0
  190. package/dist/utils/interactive.js +21 -0
  191. package/dist/utils/item-discovery.d.ts +4 -0
  192. package/dist/utils/item-discovery.js +72 -0
  193. package/dist/utils/match.d.ts +3 -0
  194. package/dist/utils/match.js +22 -0
  195. package/dist/utils/shell-detection.d.ts +20 -0
  196. package/dist/utils/shell-detection.js +41 -0
  197. package/dist/utils/task-progress.d.ts +8 -0
  198. package/dist/utils/task-progress.js +36 -0
  199. package/package.json +81 -0
  200. package/schemas/spec-driven/schema.yaml +205 -0
  201. package/schemas/spec-driven/templates/design.md +19 -0
  202. package/schemas/spec-driven/templates/proposal.md +43 -0
  203. package/schemas/spec-driven/templates/spec.md +8 -0
  204. package/schemas/spec-driven/templates/tasks.md +25 -0
  205. package/schemas/tdd/schema.yaml +213 -0
  206. package/schemas/tdd/templates/docs.md +0 -0
  207. package/schemas/tdd/templates/implementation.md +11 -0
  208. package/schemas/tdd/templates/spec.md +11 -0
  209. package/schemas/tdd/templates/test.md +11 -0
  210. package/scripts/postinstall.js +147 -0
@@ -0,0 +1,916 @@
1
+ /**
2
+ * Artifact Workflow CLI Commands
3
+ *
4
+ * This file contains all artifact workflow commands.
5
+ * Commands expose the ArtifactGraph and InstructionLoader APIs to users and agents.
6
+ */
7
+ import ora from 'ora';
8
+ import chalk from 'chalk';
9
+ import path from 'path';
10
+ import * as fs from 'fs';
11
+ import { loadChangeContext, formatChangeStatus, generateInstructions, listSchemas, listSchemasWithInfo, getSchemaDir, resolveSchema, ArtifactGraph, } from '../core/artifact-graph/index.js';
12
+ import { createChange, validateChangeName } from '../utils/change-utils.js';
13
+ import { discoverSkills } from '../core/skill-discovery.js';
14
+ import { getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getProposeChangeSkillTemplate, getExploreSkillTemplate, getVerifySkillTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxProposeCommandTemplate, getOpsxExploreCommandTemplate, getOpsxVerifyCommandTemplate } from '../core/templates/skill-templates.js';
15
+ import { FileSystemUtils } from '../utils/file-system.js';
16
+ const DEFAULT_SCHEMA = 'spec-driven';
17
+ /**
18
+ * Checks if color output is disabled via NO_COLOR env or --no-color flag.
19
+ */
20
+ function isColorDisabled() {
21
+ return process.env.NO_COLOR === '1' || process.env.NO_COLOR === 'true';
22
+ }
23
+ /**
24
+ * Gets the color function based on status.
25
+ */
26
+ function getStatusColor(status) {
27
+ if (isColorDisabled()) {
28
+ return (text) => text;
29
+ }
30
+ switch (status) {
31
+ case 'done':
32
+ return chalk.green;
33
+ case 'ready':
34
+ return chalk.yellow;
35
+ case 'blocked':
36
+ return chalk.red;
37
+ }
38
+ }
39
+ /**
40
+ * Gets the status indicator for an artifact.
41
+ */
42
+ function getStatusIndicator(status) {
43
+ const color = getStatusColor(status);
44
+ switch (status) {
45
+ case 'done':
46
+ return color('[x]');
47
+ case 'ready':
48
+ return color('[ ]');
49
+ case 'blocked':
50
+ return color('[-]');
51
+ }
52
+ }
53
+ /**
54
+ * Validates that a change exists and returns available changes if not.
55
+ * Checks directory existence directly to support scaffolded changes (without proposal.md).
56
+ */
57
+ async function validateChangeExists(changeName, projectRoot) {
58
+ const changesPath = path.join(projectRoot, 'zhuanspec', 'changes');
59
+ // Get all change directories (not just those with proposal.md)
60
+ const getAvailableChanges = async () => {
61
+ try {
62
+ const entries = await fs.promises.readdir(changesPath, { withFileTypes: true });
63
+ return entries
64
+ .filter((e) => e.isDirectory() && e.name !== 'archive' && !e.name.startsWith('.'))
65
+ .map((e) => e.name);
66
+ }
67
+ catch {
68
+ return [];
69
+ }
70
+ };
71
+ if (!changeName) {
72
+ const available = await getAvailableChanges();
73
+ if (available.length === 0) {
74
+ throw new Error('No changes found. Create one with: zhuanspec new change <name>');
75
+ }
76
+ throw new Error(`Missing required option --change. Available changes:\n ${available.join('\n ')}`);
77
+ }
78
+ // Validate change name format to prevent path traversal
79
+ const nameValidation = validateChangeName(changeName);
80
+ if (!nameValidation.valid) {
81
+ throw new Error(`Invalid change name '${changeName}': ${nameValidation.error}`);
82
+ }
83
+ // Check directory existence directly
84
+ const changePath = path.join(changesPath, changeName);
85
+ const exists = fs.existsSync(changePath) && fs.statSync(changePath).isDirectory();
86
+ if (!exists) {
87
+ const available = await getAvailableChanges();
88
+ if (available.length === 0) {
89
+ throw new Error(`Change '${changeName}' not found. No changes exist. Create one with: zhuanspec new change <name>`);
90
+ }
91
+ throw new Error(`Change '${changeName}' not found. Available changes:\n ${available.join('\n ')}`);
92
+ }
93
+ return changeName;
94
+ }
95
+ /**
96
+ * Validates that a schema exists and returns available schemas if not.
97
+ */
98
+ function validateSchemaExists(schemaName) {
99
+ const schemaDir = getSchemaDir(schemaName);
100
+ if (!schemaDir) {
101
+ const availableSchemas = listSchemas();
102
+ throw new Error(`Schema '${schemaName}' not found. Available schemas:\n ${availableSchemas.join('\n ')}`);
103
+ }
104
+ return schemaName;
105
+ }
106
+ async function statusCommand(options) {
107
+ const spinner = ora('Loading change status...').start();
108
+ try {
109
+ const projectRoot = process.cwd();
110
+ const changeName = await validateChangeExists(options.change, projectRoot);
111
+ // Validate schema if explicitly provided
112
+ if (options.schema) {
113
+ validateSchemaExists(options.schema);
114
+ }
115
+ // loadChangeContext will auto-detect schema from metadata if not provided
116
+ const context = loadChangeContext(projectRoot, changeName, options.schema);
117
+ const status = formatChangeStatus(context);
118
+ spinner.stop();
119
+ if (options.json) {
120
+ console.log(JSON.stringify(status, null, 2));
121
+ return;
122
+ }
123
+ printStatusText(status);
124
+ }
125
+ catch (error) {
126
+ spinner.stop();
127
+ throw error;
128
+ }
129
+ }
130
+ function printStatusText(status) {
131
+ const doneCount = status.artifacts.filter((a) => a.status === 'done').length;
132
+ const total = status.artifacts.length;
133
+ console.log(`Change: ${status.changeName}`);
134
+ console.log(`Schema: ${status.schemaName}`);
135
+ console.log(`Progress: ${doneCount}/${total} artifacts complete`);
136
+ console.log();
137
+ for (const artifact of status.artifacts) {
138
+ const indicator = getStatusIndicator(artifact.status);
139
+ const color = getStatusColor(artifact.status);
140
+ let line = `${indicator} ${artifact.id}`;
141
+ if (artifact.status === 'blocked' && artifact.missingDeps && artifact.missingDeps.length > 0) {
142
+ line += color(` (blocked by: ${artifact.missingDeps.join(', ')})`);
143
+ }
144
+ console.log(line);
145
+ }
146
+ if (status.isComplete) {
147
+ console.log();
148
+ console.log(chalk.green('All artifacts complete!'));
149
+ }
150
+ }
151
+ async function instructionsCommand(artifactId, options) {
152
+ const spinner = ora('Generating instructions...').start();
153
+ try {
154
+ const projectRoot = process.cwd();
155
+ const changeName = await validateChangeExists(options.change, projectRoot);
156
+ // Validate schema if explicitly provided
157
+ if (options.schema) {
158
+ validateSchemaExists(options.schema);
159
+ }
160
+ // loadChangeContext will auto-detect schema from metadata if not provided
161
+ const context = loadChangeContext(projectRoot, changeName, options.schema);
162
+ if (!artifactId) {
163
+ spinner.stop();
164
+ const validIds = context.graph.getAllArtifacts().map((a) => a.id);
165
+ throw new Error(`Missing required argument <artifact>. Valid artifacts:\n ${validIds.join('\n ')}`);
166
+ }
167
+ const artifact = context.graph.getArtifact(artifactId);
168
+ if (!artifact) {
169
+ spinner.stop();
170
+ const validIds = context.graph.getAllArtifacts().map((a) => a.id);
171
+ throw new Error(`Artifact '${artifactId}' not found in schema '${context.schemaName}'. Valid artifacts:\n ${validIds.join('\n ')}`);
172
+ }
173
+ const instructions = generateInstructions(context, artifactId);
174
+ const isBlocked = instructions.dependencies.some((d) => !d.done);
175
+ // Auto-discover skills for tasks and proposal artifacts
176
+ let discoveredSkillsJson;
177
+ if (artifactId === 'tasks' || artifactId === 'proposal') {
178
+ try {
179
+ const skills = await discoverSkills();
180
+ if (skills.length > 0) {
181
+ discoveredSkillsJson = JSON.stringify(skills.map((s) => ({ name: s.name, description: s.description })), null, 2);
182
+ }
183
+ }
184
+ catch {
185
+ // Skill discovery is best-effort, don't fail the command
186
+ }
187
+ }
188
+ spinner.stop();
189
+ if (options.json) {
190
+ const output = { ...instructions };
191
+ if (discoveredSkillsJson) {
192
+ output.discoveredSkills = JSON.parse(discoveredSkillsJson);
193
+ }
194
+ console.log(JSON.stringify(output, null, 2));
195
+ return;
196
+ }
197
+ printInstructionsText(instructions, isBlocked, discoveredSkillsJson);
198
+ }
199
+ catch (error) {
200
+ spinner.stop();
201
+ throw error;
202
+ }
203
+ }
204
+ function printInstructionsText(instructions, isBlocked, discoveredSkillsJson) {
205
+ const { artifactId, changeName, schemaName, changeDir, outputPath, description, instruction, template, projectContext, rules, dependencies, unlocks, } = instructions;
206
+ // Opening tag
207
+ console.log(`<artifact id="${artifactId}" change="${changeName}" schema="${schemaName}">`);
208
+ console.log();
209
+ // Warning for blocked artifacts
210
+ if (isBlocked) {
211
+ const missing = dependencies.filter((d) => !d.done).map((d) => d.id);
212
+ console.log('<warning>');
213
+ console.log('This artifact has unmet dependencies. Complete them first or proceed with caution.');
214
+ console.log(`Missing: ${missing.join(', ')}`);
215
+ console.log('</warning>');
216
+ console.log();
217
+ }
218
+ // Task directive
219
+ console.log('<task>');
220
+ console.log(`Create the ${artifactId} artifact for change "${changeName}".`);
221
+ console.log(description);
222
+ console.log('</task>');
223
+ console.log();
224
+ // Context (dependencies)
225
+ if (dependencies.length > 0) {
226
+ console.log('<context>');
227
+ console.log('Read these files for context before creating this artifact:');
228
+ console.log();
229
+ for (const dep of dependencies) {
230
+ const status = dep.done ? 'done' : 'missing';
231
+ const fullPath = path.join(changeDir, dep.path);
232
+ console.log(`<dependency id="${dep.id}" status="${status}">`);
233
+ console.log(` <path>${fullPath}</path>`);
234
+ console.log(` <description>${dep.description}</description>`);
235
+ console.log('</dependency>');
236
+ }
237
+ console.log('</context>');
238
+ console.log();
239
+ }
240
+ // Project context (from config.yaml)
241
+ if (projectContext) {
242
+ console.log('<project_context>');
243
+ console.log(projectContext.trim());
244
+ console.log('</project_context>');
245
+ console.log();
246
+ }
247
+ // Rules (global + per-artifact from config.yaml)
248
+ if (rules) {
249
+ console.log('<rules>');
250
+ console.log(rules.trim());
251
+ console.log('</rules>');
252
+ console.log();
253
+ }
254
+ // Output location
255
+ console.log('<output>');
256
+ console.log(`Write to: ${path.join(changeDir, outputPath)}`);
257
+ console.log('</output>');
258
+ console.log();
259
+ // Instruction (guidance)
260
+ if (instruction) {
261
+ console.log('<instruction>');
262
+ console.log(instruction.trim());
263
+ console.log('</instruction>');
264
+ console.log();
265
+ }
266
+ // Discovered skills (auto-injected for tasks artifact)
267
+ if (discoveredSkillsJson) {
268
+ console.log('<discovered_skills>');
269
+ console.log('The following skills are available in the current environment.');
270
+ console.log('Use @skill:<name> tags to annotate tasks that match these skills.');
271
+ console.log();
272
+ console.log(discoveredSkillsJson);
273
+ console.log('</discovered_skills>');
274
+ console.log();
275
+ }
276
+ // Template
277
+ console.log('<template>');
278
+ console.log(template.trim());
279
+ console.log('</template>');
280
+ console.log();
281
+ // Success criteria placeholder
282
+ console.log('<success_criteria>');
283
+ console.log('<!-- To be defined in schema validation rules -->');
284
+ console.log('</success_criteria>');
285
+ console.log();
286
+ // Unlocks
287
+ if (unlocks.length > 0) {
288
+ console.log('<unlocks>');
289
+ console.log(`Completing this artifact enables: ${unlocks.join(', ')}`);
290
+ console.log('</unlocks>');
291
+ console.log();
292
+ }
293
+ // Closing tag
294
+ console.log('</artifact>');
295
+ }
296
+ /**
297
+ * Parses tasks.md content and extracts task items with their completion status.
298
+ */
299
+ function parseTasksFile(content) {
300
+ const tasks = [];
301
+ const lines = content.split('\n');
302
+ let taskIndex = 0;
303
+ for (const line of lines) {
304
+ // Match checkbox patterns: - [ ] or - [x] or - [X]
305
+ const checkboxMatch = line.match(/^[-*]\s*\[([ xX])\]\s*(.+)$/);
306
+ if (checkboxMatch) {
307
+ taskIndex++;
308
+ const done = checkboxMatch[1].toLowerCase() === 'x';
309
+ const description = checkboxMatch[2].trim();
310
+ tasks.push({
311
+ id: `${taskIndex}`,
312
+ description,
313
+ done,
314
+ });
315
+ }
316
+ }
317
+ return tasks;
318
+ }
319
+ /**
320
+ * Checks if an artifact output exists in the change directory.
321
+ * Supports glob patterns (e.g., "specs/*.md") by verifying at least one matching file exists.
322
+ */
323
+ function artifactOutputExists(changeDir, generates) {
324
+ // Normalize the generates path to use platform-specific separators
325
+ const normalizedGenerates = generates.split('/').join(path.sep);
326
+ const fullPath = path.join(changeDir, normalizedGenerates);
327
+ // If it's a glob pattern (contains ** or *), check for matching files
328
+ if (generates.includes('*')) {
329
+ // Extract the directory part before the glob pattern
330
+ const parts = normalizedGenerates.split(path.sep);
331
+ const dirParts = [];
332
+ let patternPart = '';
333
+ for (const part of parts) {
334
+ if (part.includes('*')) {
335
+ patternPart = part;
336
+ break;
337
+ }
338
+ dirParts.push(part);
339
+ }
340
+ const dirPath = path.join(changeDir, ...dirParts);
341
+ // Check if directory exists
342
+ if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
343
+ return false;
344
+ }
345
+ // Extract expected extension from pattern (e.g., "*.md" -> ".md")
346
+ const extMatch = patternPart.match(/\*(\.[a-zA-Z0-9]+)$/);
347
+ const expectedExt = extMatch ? extMatch[1] : null;
348
+ // Recursively check for matching files
349
+ const hasMatchingFiles = (dir) => {
350
+ try {
351
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
352
+ for (const entry of entries) {
353
+ if (entry.isDirectory()) {
354
+ // For ** patterns, recurse into subdirectories
355
+ if (generates.includes('**') && hasMatchingFiles(path.join(dir, entry.name))) {
356
+ return true;
357
+ }
358
+ }
359
+ else if (entry.isFile()) {
360
+ // Check if file matches expected extension (or any file if no extension specified)
361
+ if (!expectedExt || entry.name.endsWith(expectedExt)) {
362
+ return true;
363
+ }
364
+ }
365
+ }
366
+ }
367
+ catch {
368
+ return false;
369
+ }
370
+ return false;
371
+ };
372
+ return hasMatchingFiles(dirPath);
373
+ }
374
+ return fs.existsSync(fullPath);
375
+ }
376
+ /**
377
+ * Generates apply instructions for implementing tasks from a change.
378
+ * Schema-aware: reads apply phase configuration from schema to determine
379
+ * required artifacts, tracking file, and instruction.
380
+ */
381
+ async function generateApplyInstructions(projectRoot, changeName, schemaName) {
382
+ // loadChangeContext will auto-detect schema from metadata if not provided
383
+ const context = loadChangeContext(projectRoot, changeName, schemaName);
384
+ const changeDir = path.join(projectRoot, 'zhuanspec', 'changes', changeName);
385
+ // Get the full schema to access the apply phase configuration
386
+ const schema = resolveSchema(context.schemaName);
387
+ const applyConfig = schema.apply;
388
+ // Determine required artifacts and tracking file from schema
389
+ // Fallback: if no apply block, require all artifacts
390
+ const requiredArtifactIds = applyConfig?.requires ?? schema.artifacts.map((a) => a.id);
391
+ const tracksFile = applyConfig?.tracks ?? null;
392
+ const schemaInstruction = applyConfig?.instruction ?? null;
393
+ // Check which required artifacts are missing
394
+ const missingArtifacts = [];
395
+ for (const artifactId of requiredArtifactIds) {
396
+ const artifact = schema.artifacts.find((a) => a.id === artifactId);
397
+ if (artifact && !artifactOutputExists(changeDir, artifact.generates)) {
398
+ missingArtifacts.push(artifactId);
399
+ }
400
+ }
401
+ // Build context files from all existing artifacts in schema
402
+ const contextFiles = {};
403
+ for (const artifact of schema.artifacts) {
404
+ if (artifactOutputExists(changeDir, artifact.generates)) {
405
+ contextFiles[artifact.id] = path.join(changeDir, artifact.generates);
406
+ }
407
+ }
408
+ // Parse tasks if tracking file exists
409
+ let tasks = [];
410
+ let tracksFileExists = false;
411
+ if (tracksFile) {
412
+ const tracksPath = path.join(changeDir, tracksFile);
413
+ tracksFileExists = fs.existsSync(tracksPath);
414
+ if (tracksFileExists) {
415
+ const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
416
+ tasks = parseTasksFile(tasksContent);
417
+ }
418
+ }
419
+ // Calculate progress
420
+ const total = tasks.length;
421
+ const complete = tasks.filter((t) => t.done).length;
422
+ const remaining = total - complete;
423
+ // Determine state and instruction
424
+ let state;
425
+ let instruction;
426
+ if (missingArtifacts.length > 0) {
427
+ state = 'blocked';
428
+ instruction = `Cannot apply this change yet. Missing artifacts: ${missingArtifacts.join(', ')}.\nUse the zhuanspec-continue-change skill to create the missing artifacts first.`;
429
+ }
430
+ else if (tracksFile && !tracksFileExists) {
431
+ // Tracking file configured but doesn't exist yet
432
+ const tracksFilename = path.basename(tracksFile);
433
+ state = 'blocked';
434
+ instruction = `The ${tracksFilename} file is missing and must be created.\nUse zhuanspec-continue-change to generate the tracking file.`;
435
+ }
436
+ else if (tracksFile && tracksFileExists && total === 0) {
437
+ // Tracking file exists but contains no tasks
438
+ const tracksFilename = path.basename(tracksFile);
439
+ state = 'blocked';
440
+ instruction = `The ${tracksFilename} file exists but contains no tasks.\nAdd tasks to ${tracksFilename} or regenerate it with zhuanspec-continue-change.`;
441
+ }
442
+ else if (tracksFile && remaining === 0 && total > 0) {
443
+ state = 'all_done';
444
+ instruction = `All tasks are complete! This change is ready for code review or archiving.
445
+
446
+ **Next Step: Code Review (Optional)**
447
+
448
+ When all tasks are complete, you should:
449
+ 1. Use the **AskUserQuestion tool** to ask the user: "所有任务已完成。是否进行代码审查?" (or "All tasks are complete. Would you like to perform a code review?")
450
+ 2. Provide options:
451
+ - "是,进行代码审查" / "Yes, perform code review"
452
+ - "跳过,直接归档" / "Skip, proceed to archive"
453
+ 3. Wait for user selection before proceeding.
454
+
455
+ If the user selects code review, review the implemented code for:
456
+ - Code standards and conventions compliance
457
+ - Logic correctness
458
+ - Performance issues
459
+ - Security concerns
460
+ - Best practices adherence
461
+
462
+ See the apply command template for detailed code review workflow instructions.`;
463
+ }
464
+ else if (!tracksFile) {
465
+ // No tracking file (e.g., TDD schema) - ready to apply
466
+ state = 'ready';
467
+ instruction = schemaInstruction?.trim() ?? 'All required artifacts complete. Proceed with implementation.';
468
+ }
469
+ else {
470
+ state = 'ready';
471
+ instruction = schemaInstruction?.trim() ?? 'Read context files, work through pending tasks, mark complete as you go.\nPause if you hit blockers or need clarification.';
472
+ }
473
+ return {
474
+ changeName,
475
+ changeDir,
476
+ schemaName: context.schemaName,
477
+ contextFiles,
478
+ progress: { total, complete, remaining },
479
+ tasks,
480
+ state,
481
+ missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
482
+ instruction,
483
+ };
484
+ }
485
+ async function applyInstructionsCommand(options) {
486
+ const spinner = ora('Generating apply instructions...').start();
487
+ try {
488
+ const projectRoot = process.cwd();
489
+ const changeName = await validateChangeExists(options.change, projectRoot);
490
+ // Validate schema if explicitly provided
491
+ if (options.schema) {
492
+ validateSchemaExists(options.schema);
493
+ }
494
+ // generateApplyInstructions uses loadChangeContext which auto-detects schema
495
+ const instructions = await generateApplyInstructions(projectRoot, changeName, options.schema);
496
+ spinner.stop();
497
+ if (options.json) {
498
+ console.log(JSON.stringify(instructions, null, 2));
499
+ return;
500
+ }
501
+ printApplyInstructionsText(instructions);
502
+ }
503
+ catch (error) {
504
+ spinner.stop();
505
+ throw error;
506
+ }
507
+ }
508
+ function printApplyInstructionsText(instructions) {
509
+ const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction } = instructions;
510
+ console.log(`## Apply: ${changeName}`);
511
+ console.log(`Schema: ${schemaName}`);
512
+ console.log();
513
+ // Warning for blocked state
514
+ if (state === 'blocked' && missingArtifacts) {
515
+ console.log('### ⚠️ Blocked');
516
+ console.log();
517
+ console.log(`Missing artifacts: ${missingArtifacts.join(', ')}`);
518
+ console.log('Use the zhuanspec-continue-change skill to create these first.');
519
+ console.log();
520
+ }
521
+ // Context files (dynamically from schema)
522
+ const contextFileEntries = Object.entries(contextFiles);
523
+ if (contextFileEntries.length > 0) {
524
+ console.log('### Context Files');
525
+ for (const [artifactId, filePath] of contextFileEntries) {
526
+ console.log(`- ${artifactId}: ${filePath}`);
527
+ }
528
+ console.log();
529
+ }
530
+ // Progress (only show if we have tracking)
531
+ if (progress.total > 0 || tasks.length > 0) {
532
+ console.log('### Progress');
533
+ if (state === 'all_done') {
534
+ console.log(`${progress.complete}/${progress.total} complete ✓`);
535
+ }
536
+ else {
537
+ console.log(`${progress.complete}/${progress.total} complete`);
538
+ }
539
+ console.log();
540
+ }
541
+ // Tasks
542
+ if (tasks.length > 0) {
543
+ console.log('### Tasks');
544
+ for (const task of tasks) {
545
+ const checkbox = task.done ? '[x]' : '[ ]';
546
+ console.log(`- ${checkbox} ${task.description}`);
547
+ }
548
+ console.log();
549
+ }
550
+ // Instruction
551
+ console.log('### Instruction');
552
+ console.log(instruction);
553
+ // Code review prompt for all_done state
554
+ if (state === 'all_done') {
555
+ console.log();
556
+ console.log('### 📋 Code Review (Optional)');
557
+ console.log();
558
+ console.log('All tasks are complete. You should now:');
559
+ console.log();
560
+ console.log('1. **Prompt for code review** using the **AskUserQuestion tool**:');
561
+ console.log(' - Ask: "所有任务已完成。是否进行代码审查?" (or "All tasks are complete. Would you like to perform a code review?")');
562
+ console.log(' - Provide options:');
563
+ console.log(' • "是,进行代码审查" / "Yes, perform code review"');
564
+ console.log(' • "跳过,直接归档" / "Skip, proceed to archive"');
565
+ console.log(' - Wait for user selection before proceeding');
566
+ console.log();
567
+ console.log('2. **If user selects code review**, review the implemented code for:');
568
+ console.log(' - Code standards and conventions compliance');
569
+ console.log(' - Logic correctness');
570
+ console.log(' - Performance issues');
571
+ console.log(' - Security concerns');
572
+ console.log(' - Best practices adherence');
573
+ console.log();
574
+ console.log('3. **After code review** (or if skipped), proceed to archive workflow.');
575
+ console.log();
576
+ console.log('See the apply command template for detailed code review workflow instructions.');
577
+ }
578
+ }
579
+ async function templatesCommand(options) {
580
+ const spinner = ora('Loading templates...').start();
581
+ try {
582
+ const schemaName = validateSchemaExists(options.schema ?? DEFAULT_SCHEMA);
583
+ const schema = resolveSchema(schemaName);
584
+ const graph = ArtifactGraph.fromSchema(schema);
585
+ const schemaDir = getSchemaDir(schemaName);
586
+ // Determine if this is a user override or package built-in
587
+ const { getUserSchemasDir } = await import('../core/artifact-graph/resolver.js');
588
+ const userSchemasDir = getUserSchemasDir();
589
+ const isUserOverride = schemaDir.startsWith(userSchemasDir);
590
+ const templates = graph.getAllArtifacts().map((artifact) => ({
591
+ artifactId: artifact.id,
592
+ templatePath: path.join(schemaDir, 'templates', artifact.template),
593
+ source: isUserOverride ? 'user' : 'package',
594
+ }));
595
+ spinner.stop();
596
+ if (options.json) {
597
+ const output = {};
598
+ for (const t of templates) {
599
+ output[t.artifactId] = { path: t.templatePath, source: t.source };
600
+ }
601
+ console.log(JSON.stringify(output, null, 2));
602
+ return;
603
+ }
604
+ console.log(`Schema: ${schemaName}`);
605
+ console.log(`Source: ${isUserOverride ? 'user override' : 'package built-in'}`);
606
+ console.log();
607
+ for (const t of templates) {
608
+ console.log(`${t.artifactId}:`);
609
+ console.log(` ${t.templatePath}`);
610
+ }
611
+ }
612
+ catch (error) {
613
+ spinner.stop();
614
+ throw error;
615
+ }
616
+ }
617
+ async function newChangeCommand(name, options) {
618
+ if (!name) {
619
+ throw new Error('Missing required argument <name>');
620
+ }
621
+ const validation = validateChangeName(name);
622
+ if (!validation.valid) {
623
+ throw new Error(validation.error);
624
+ }
625
+ // Validate schema if provided
626
+ if (options.schema) {
627
+ validateSchemaExists(options.schema);
628
+ }
629
+ const schemaDisplay = options.schema ? ` with schema '${options.schema}'` : '';
630
+ const spinner = ora(`Creating change '${name}'${schemaDisplay}...`).start();
631
+ try {
632
+ const projectRoot = process.cwd();
633
+ await createChange(projectRoot, name, { schema: options.schema });
634
+ // If description provided, create README.md with description
635
+ if (options.description) {
636
+ const { promises: fs } = await import('fs');
637
+ const changeDir = path.join(projectRoot, 'zhuanspec', 'changes', name);
638
+ const readmePath = path.join(changeDir, 'README.md');
639
+ await fs.writeFile(readmePath, `# ${name}\n\n${options.description}\n`, 'utf-8');
640
+ }
641
+ const schemaUsed = options.schema ?? DEFAULT_SCHEMA;
642
+ spinner.succeed(`Created change '${name}' at zhuanspec/changes/${name}/ (schema: ${schemaUsed})`);
643
+ }
644
+ catch (error) {
645
+ spinner.fail(`Failed to create change '${name}'`);
646
+ throw error;
647
+ }
648
+ }
649
+ // -----------------------------------------------------------------------------
650
+ // Artifact Experimental Setup Command
651
+ // -----------------------------------------------------------------------------
652
+ /**
653
+ * Generates Agent Skills and slash commands for the experimental artifact workflow.
654
+ * Creates .claude/skills/ directory with SKILL.md files following Agent Skills spec.
655
+ * Creates .claude/commands/opsx/ directory with slash command files.
656
+ */
657
+ async function artifactExperimentalSetupCommand() {
658
+ const spinner = ora('Setting up experimental artifact workflow...').start();
659
+ try {
660
+ const projectRoot = process.cwd();
661
+ const skillsDir = path.join(projectRoot, '.claude', 'skills');
662
+ const commandsDir = path.join(projectRoot, '.claude', 'commands', 'opsx');
663
+ // Get skill templates
664
+ const newChangeSkill = getNewChangeSkillTemplate();
665
+ const continueChangeSkill = getContinueChangeSkillTemplate();
666
+ const applyChangeSkill = getApplyChangeSkillTemplate();
667
+ const ffChangeSkill = getFfChangeSkillTemplate();
668
+ const syncSpecsSkill = getSyncSpecsSkillTemplate();
669
+ const archiveChangeSkill = getArchiveChangeSkillTemplate();
670
+ const proposeChangeSkill = getProposeChangeSkillTemplate();
671
+ const exploreSkill = getExploreSkillTemplate();
672
+ const verifySkill = getVerifySkillTemplate();
673
+ // Get command templates
674
+ const newCommand = getOpsxNewCommandTemplate();
675
+ const continueCommand = getOpsxContinueCommandTemplate();
676
+ const applyCommand = getOpsxApplyCommandTemplate();
677
+ const ffCommand = getOpsxFfCommandTemplate();
678
+ const syncCommand = getOpsxSyncCommandTemplate();
679
+ const archiveCommand = getOpsxArchiveCommandTemplate();
680
+ const proposeCommand = getOpsxProposeCommandTemplate();
681
+ const exploreCommand = getOpsxExploreCommandTemplate();
682
+ const verifyCommand = getOpsxVerifyCommandTemplate();
683
+ // Create skill directories and SKILL.md files
684
+ const skills = [
685
+ { template: proposeChangeSkill, dirName: 'zhuanspec-propose-change' },
686
+ { template: exploreSkill, dirName: 'zhuanspec-explore' },
687
+ { template: newChangeSkill, dirName: 'zhuanspec-new-change' },
688
+ { template: continueChangeSkill, dirName: 'zhuanspec-continue-change' },
689
+ { template: applyChangeSkill, dirName: 'zhuanspec-apply-change' },
690
+ { template: ffChangeSkill, dirName: 'zhuanspec-ff-change' },
691
+ { template: verifySkill, dirName: 'zhuanspec-verify' },
692
+ { template: syncSpecsSkill, dirName: 'zhuanspec-sync-specs' },
693
+ { template: archiveChangeSkill, dirName: 'zhuanspec-archive-change' },
694
+ ];
695
+ const createdSkillFiles = [];
696
+ for (const { template, dirName } of skills) {
697
+ const skillDir = path.join(skillsDir, dirName);
698
+ const skillFile = path.join(skillDir, 'SKILL.md');
699
+ // Generate SKILL.md content with YAML frontmatter
700
+ const skillContent = `---
701
+ name: ${template.name}
702
+ description: ${template.description}
703
+ ---
704
+
705
+ ${template.instructions}
706
+ `;
707
+ // Write the skill file
708
+ await FileSystemUtils.writeFile(skillFile, skillContent);
709
+ createdSkillFiles.push(path.relative(projectRoot, skillFile));
710
+ }
711
+ // Create slash command files
712
+ const commands = [
713
+ { template: proposeCommand, fileName: 'propose.md' },
714
+ { template: exploreCommand, fileName: 'explore.md' },
715
+ { template: newCommand, fileName: 'new.md' },
716
+ { template: continueCommand, fileName: 'continue.md' },
717
+ { template: applyCommand, fileName: 'apply.md' },
718
+ { template: ffCommand, fileName: 'ff.md' },
719
+ { template: verifyCommand, fileName: 'verify.md' },
720
+ { template: syncCommand, fileName: 'sync.md' },
721
+ { template: archiveCommand, fileName: 'archive.md' },
722
+ ];
723
+ const createdCommandFiles = [];
724
+ for (const { template, fileName } of commands) {
725
+ const commandFile = path.join(commandsDir, fileName);
726
+ // Generate command content with YAML frontmatter
727
+ const commandContent = `---
728
+ name: ${template.name}
729
+ description: ${template.description}
730
+ category: ${template.category}
731
+ tags: [${template.tags.join(', ')}]
732
+ ---
733
+
734
+ ${template.content}
735
+ `;
736
+ // Write the command file
737
+ await FileSystemUtils.writeFile(commandFile, commandContent);
738
+ createdCommandFiles.push(path.relative(projectRoot, commandFile));
739
+ }
740
+ spinner.succeed('Experimental artifact workflow setup complete!');
741
+ // Print success message
742
+ console.log();
743
+ console.log(chalk.bold('🧪 Experimental Artifact Workflow Setup Complete'));
744
+ console.log();
745
+ console.log(chalk.bold('Skills Created:'));
746
+ for (const file of createdSkillFiles) {
747
+ console.log(chalk.green(' ✓ ' + file));
748
+ }
749
+ console.log();
750
+ console.log(chalk.bold('Slash Commands Created:'));
751
+ for (const file of createdCommandFiles) {
752
+ console.log(chalk.green(' ✓ ' + file));
753
+ }
754
+ console.log();
755
+ console.log(chalk.bold('📖 Usage:'));
756
+ console.log();
757
+ console.log(' ' + chalk.cyan('Skills') + ' work automatically in compatible editors:');
758
+ console.log(' • Claude Code - Auto-detected, ready to use');
759
+ console.log(' • Cursor - Enable in Settings → Rules → Import Settings');
760
+ console.log(' • Windsurf - Auto-imports from .claude directory');
761
+ console.log();
762
+ console.log(' Ask Claude naturally:');
763
+ console.log(' • "I want to propose a change to add <feature>"');
764
+ console.log(' • "Help me explore how to improve <area>"');
765
+ console.log(' • "Continue working on this change"');
766
+ console.log(' • "Implement the tasks for this change"');
767
+ console.log(' • "Verify the implementation matches specs"');
768
+ console.log();
769
+ console.log(' ' + chalk.cyan('Slash Commands') + ' for explicit invocation:');
770
+ console.log(' • /opsx:propose - Create a complete change proposal in one step');
771
+ console.log(' • /opsx:explore - Think through ideas before committing');
772
+ console.log(' • /opsx:new - Start a new change scaffold');
773
+ console.log(' • /opsx:continue - Create the next artifact');
774
+ console.log(' • /opsx:apply - Implement tasks');
775
+ console.log(' • /opsx:ff - Fast-forward: create all artifacts at once');
776
+ console.log(' • /opsx:verify - Validate implementation matches specs');
777
+ console.log(' • /opsx:sync - Sync delta specs to main specs');
778
+ console.log(' • /opsx:archive - Archive a completed change');
779
+ console.log();
780
+ console.log(chalk.yellow('💡 This is an experimental feature.'));
781
+ console.log(' Feedback welcome at: https://github.com/Fission-AI/ZhuanSpec/issues');
782
+ console.log();
783
+ }
784
+ catch (error) {
785
+ spinner.fail('Failed to setup experimental artifact workflow');
786
+ throw error;
787
+ }
788
+ }
789
+ async function schemasCommand(options) {
790
+ const schemas = listSchemasWithInfo();
791
+ if (options.json) {
792
+ console.log(JSON.stringify(schemas, null, 2));
793
+ return;
794
+ }
795
+ console.log('Available schemas:');
796
+ console.log();
797
+ for (const schema of schemas) {
798
+ const sourceLabel = schema.source === 'user' ? chalk.dim(' (user override)') : '';
799
+ console.log(` ${chalk.bold(schema.name)}${sourceLabel}`);
800
+ console.log(` ${schema.description}`);
801
+ console.log(` Artifacts: ${schema.artifacts.join(' → ')}`);
802
+ console.log();
803
+ }
804
+ }
805
+ // -----------------------------------------------------------------------------
806
+ // Command Registration
807
+ // -----------------------------------------------------------------------------
808
+ /**
809
+ * Registers all artifact workflow commands on the given program.
810
+ * All commands are marked as experimental in their help text.
811
+ */
812
+ export function registerArtifactWorkflowCommands(program) {
813
+ // Status command
814
+ program
815
+ .command('status')
816
+ .description('Display artifact completion status for a change')
817
+ .option('--change <id>', 'Change name to show status for')
818
+ .option('--schema <name>', 'Schema override (auto-detected from .zhuanspec.yaml)')
819
+ .option('--json', 'Output as JSON')
820
+ .action(async (options) => {
821
+ try {
822
+ await statusCommand(options);
823
+ }
824
+ catch (error) {
825
+ console.log();
826
+ ora().fail(`Error: ${error.message}`);
827
+ process.exit(1);
828
+ }
829
+ });
830
+ // Instructions command
831
+ program
832
+ .command('instructions [artifact]')
833
+ .description('Output enriched instructions for creating an artifact or applying tasks')
834
+ .option('--change <id>', 'Change name')
835
+ .option('--schema <name>', 'Schema override (auto-detected from .zhuanspec.yaml)')
836
+ .option('--json', 'Output as JSON')
837
+ .action(async (artifactId, options) => {
838
+ try {
839
+ // Special case: "apply" is not an artifact, but a command to get apply instructions
840
+ if (artifactId === 'apply') {
841
+ await applyInstructionsCommand(options);
842
+ }
843
+ else {
844
+ await instructionsCommand(artifactId, options);
845
+ }
846
+ }
847
+ catch (error) {
848
+ console.log();
849
+ ora().fail(`Error: ${error.message}`);
850
+ process.exit(1);
851
+ }
852
+ });
853
+ // Templates command
854
+ program
855
+ .command('templates')
856
+ .description('Show resolved template paths for all artifacts in a schema')
857
+ .option('--schema <name>', `Schema to use (default: ${DEFAULT_SCHEMA})`)
858
+ .option('--json', 'Output as JSON mapping artifact IDs to template paths')
859
+ .action(async (options) => {
860
+ try {
861
+ await templatesCommand(options);
862
+ }
863
+ catch (error) {
864
+ console.log();
865
+ ora().fail(`Error: ${error.message}`);
866
+ process.exit(1);
867
+ }
868
+ });
869
+ // Schemas command
870
+ program
871
+ .command('schemas')
872
+ .description('List available workflow schemas with descriptions')
873
+ .option('--json', 'Output as JSON (for agent use)')
874
+ .action(async (options) => {
875
+ try {
876
+ await schemasCommand(options);
877
+ }
878
+ catch (error) {
879
+ console.log();
880
+ ora().fail(`Error: ${error.message}`);
881
+ process.exit(1);
882
+ }
883
+ });
884
+ // New command group with change subcommand
885
+ const newCmd = program.command('new').description('Create new items');
886
+ newCmd
887
+ .command('change <name>')
888
+ .description('Create a new change directory')
889
+ .option('--description <text>', 'Description to add to README.md')
890
+ .option('--schema <name>', `Workflow schema to use (default: ${DEFAULT_SCHEMA})`)
891
+ .action(async (name, options) => {
892
+ try {
893
+ await newChangeCommand(name, options);
894
+ }
895
+ catch (error) {
896
+ console.log();
897
+ ora().fail(`Error: ${error.message}`);
898
+ process.exit(1);
899
+ }
900
+ });
901
+ // Artifact experimental setup command
902
+ program
903
+ .command('artifact-experimental-setup')
904
+ .description('Setup Agent Skills for the artifact workflow')
905
+ .action(async () => {
906
+ try {
907
+ await artifactExperimentalSetupCommand();
908
+ }
909
+ catch (error) {
910
+ console.log();
911
+ ora().fail(`Error: ${error.message}`);
912
+ process.exit(1);
913
+ }
914
+ });
915
+ }
916
+ //# sourceMappingURL=artifact-workflow.js.map