@dedesfr/prompter 0.9.0 → 1.0.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 (216) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +105 -77
  3. package/dist/cli/index.js +25 -1
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +32 -9
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/login.d.ts +4 -0
  9. package/dist/commands/login.d.ts.map +1 -0
  10. package/dist/commands/login.js +56 -0
  11. package/dist/commands/login.js.map +1 -0
  12. package/dist/commands/logout.d.ts +4 -0
  13. package/dist/commands/logout.d.ts.map +1 -0
  14. package/dist/commands/logout.js +14 -0
  15. package/dist/commands/logout.js.map +1 -0
  16. package/dist/commands/update.d.ts.map +1 -1
  17. package/dist/commands/update.js +18 -5
  18. package/dist/commands/update.js.map +1 -1
  19. package/dist/commands/whoami.d.ts +4 -0
  20. package/dist/commands/whoami.d.ts.map +1 -0
  21. package/dist/commands/whoami.js +42 -0
  22. package/dist/commands/whoami.js.map +1 -0
  23. package/dist/core/auth-store.d.ts +10 -0
  24. package/dist/core/auth-store.d.ts.map +1 -0
  25. package/dist/core/auth-store.js +39 -0
  26. package/dist/core/auth-store.js.map +1 -0
  27. package/dist/core/registry.d.ts +18 -0
  28. package/dist/core/registry.d.ts.map +1 -0
  29. package/dist/core/registry.js +94 -0
  30. package/dist/core/registry.js.map +1 -0
  31. package/package.json +7 -1
  32. package/AGENTS.md +0 -123
  33. package/CLAUDE.md +0 -17
  34. package/build.js +0 -20
  35. package/convex-setup.md +0 -403
  36. package/prompt/ai-humanizer.md +0 -45
  37. package/prompt/api-contract-generator.md +0 -234
  38. package/prompt/apply.md +0 -17
  39. package/prompt/archive.md +0 -21
  40. package/prompt/design-system.md +0 -210
  41. package/prompt/document-explainer.md +0 -149
  42. package/prompt/epic-generator.md +0 -198
  43. package/prompt/epic-single.md +0 -47
  44. package/prompt/erd-generator.md +0 -130
  45. package/prompt/fsd-generator.md +0 -157
  46. package/prompt/prd-agent-generator.md +0 -147
  47. package/prompt/prd-generator.md +0 -195
  48. package/prompt/product-brief.md +0 -289
  49. package/prompt/proposal.md +0 -22
  50. package/prompt/qa-test-scenario.md +0 -133
  51. package/prompt/skill-creator.md +0 -350
  52. package/prompt/story-generator.md +0 -278
  53. package/prompt/story-single.md +0 -70
  54. package/prompt/tdd-generator.md +0 -294
  55. package/prompt/tdd-lite-generator.md +0 -224
  56. package/prompt/wireframe-generator.md +0 -219
  57. package/skills/ai-context-generator/SKILL.md +0 -54
  58. package/skills/ai-context-generator/references/AGENTS.template.md +0 -83
  59. package/skills/ai-context-generator/references/CLAUDE.template.md +0 -39
  60. package/skills/ai-context-generator/references/behavioral-guidelines.md +0 -71
  61. package/skills/ai-context-generator/references/discovery-checklist.md +0 -40
  62. package/skills/ai-context-generator/references/examples/AGENTS.good.md +0 -103
  63. package/skills/ai-context-generator/references/extraction-checklist.md +0 -23
  64. package/skills/ai-context-generator/references/overlays/laravel.md +0 -44
  65. package/skills/ai-humanizer/SKILL.md +0 -50
  66. package/skills/api-contract-generator/SKILL.md +0 -243
  67. package/skills/apply/SKILL.md +0 -23
  68. package/skills/archive/SKILL.md +0 -27
  69. package/skills/cerebro/SKILL.md +0 -187
  70. package/skills/cerebro/references/agents.md +0 -213
  71. package/skills/code-review/SKILL.md +0 -373
  72. package/skills/code-review/assets/report-template-agent.md +0 -212
  73. package/skills/code-review/assets/report-template-compact.md +0 -81
  74. package/skills/code-review/assets/report-template-full.md +0 -264
  75. package/skills/code-review/assets/report-template-human.md +0 -168
  76. package/skills/code-review/references/universal-patterns.md +0 -495
  77. package/skills/design-md/README.md +0 -34
  78. package/skills/design-md/SKILL.md +0 -172
  79. package/skills/design-md/examples/DESIGN.md +0 -154
  80. package/skills/design-system/SKILL.md +0 -216
  81. package/skills/design-system-generator/SKILL.md +0 -324
  82. package/skills/design-system-generator/assets/design-system-template.md +0 -348
  83. package/skills/design-system-generator/references/extraction-patterns.md +0 -321
  84. package/skills/doc-builder/SKILL.md +0 -115
  85. package/skills/doc-builder/references/ui-patterns.md +0 -394
  86. package/skills/document-explainer/SKILL.md +0 -155
  87. package/skills/document-translator/SKILL.md +0 -58
  88. package/skills/enhance/SKILL.md +0 -47
  89. package/skills/enhance-prompt/README.md +0 -34
  90. package/skills/enhance-prompt/SKILL.md +0 -204
  91. package/skills/enhance-prompt/references/KEYWORDS.md +0 -114
  92. package/skills/epic-generator/SKILL.md +0 -204
  93. package/skills/epic-single/SKILL.md +0 -63
  94. package/skills/erd-generator/SKILL.md +0 -138
  95. package/skills/feature-planner/SKILL.md +0 -305
  96. package/skills/feature-planner/assets/implementation-plan-template.md +0 -85
  97. package/skills/frontend-design/LICENSE.txt +0 -177
  98. package/skills/frontend-design/SKILL.md +0 -42
  99. package/skills/fsd-generator/SKILL.md +0 -163
  100. package/skills/gamma-builder/SKILL.md +0 -134
  101. package/skills/laravel-code-review/SKILL.md +0 -383
  102. package/skills/laravel-code-review/assets/report-template-agent.md +0 -195
  103. package/skills/laravel-code-review/assets/report-template-compact.md +0 -79
  104. package/skills/laravel-code-review/assets/report-template-full.md +0 -253
  105. package/skills/laravel-code-review/assets/report-template-human.md +0 -159
  106. package/skills/laravel-code-review/references/laravel-patterns.md +0 -571
  107. package/skills/laravel-code-review/references/php84-features.md +0 -442
  108. package/skills/mcp-builder/LICENSE.txt +0 -202
  109. package/skills/mcp-builder/SKILL.md +0 -236
  110. package/skills/mcp-builder/reference/evaluation.md +0 -602
  111. package/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  112. package/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  113. package/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  114. package/skills/mcp-builder/scripts/connections.py +0 -151
  115. package/skills/mcp-builder/scripts/evaluation.py +0 -373
  116. package/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  117. package/skills/mcp-builder/scripts/requirements.txt +0 -2
  118. package/skills/meeting-notes/SKILL.md +0 -159
  119. package/skills/meeting-notes/evals/evals.json +0 -23
  120. package/skills/prd-agent-generator/SKILL.md +0 -132
  121. package/skills/prd-generator/SKILL.md +0 -211
  122. package/skills/product-brief/SKILL.md +0 -141
  123. package/skills/project-orchestrator/SKILL.md +0 -487
  124. package/skills/project-orchestrator/assets/caddy-vps-setup.md +0 -180
  125. package/skills/project-orchestrator/assets/plan-summary-template.md +0 -159
  126. package/skills/prompter-specs/SKILL.md +0 -115
  127. package/skills/prompter-workflow/SKILL.md +0 -166
  128. package/skills/prompter-workflow/evals/evals.json +0 -89
  129. package/skills/proposal/SKILL.md +0 -28
  130. package/skills/qa-test-scenario/SKILL.md +0 -149
  131. package/skills/skill-creator/SKILL.md +0 -173
  132. package/skills/sph-generator/SKILL.md +0 -488
  133. package/skills/story-generator/SKILL.md +0 -285
  134. package/skills/story-single/SKILL.md +0 -86
  135. package/skills/tdd-generator/SKILL.md +0 -300
  136. package/skills/tdd-lite-generator/SKILL.md +0 -230
  137. package/skills/ui-ux-pro/SKILL.md +0 -199
  138. package/skills/ui-ux-pro/assets/design-spec-template.md +0 -173
  139. package/skills/ui-ux-pro/references/component-patterns.md +0 -255
  140. package/skills/ui-ux-pro/references/design-principles.md +0 -167
  141. package/skills/wireframe-generator/SKILL.md +0 -227
  142. package/src/cli/index.ts +0 -223
  143. package/src/commands/archive.ts +0 -302
  144. package/src/commands/change.ts +0 -292
  145. package/src/commands/config.ts +0 -233
  146. package/src/commands/guide.ts +0 -50
  147. package/src/commands/init.ts +0 -597
  148. package/src/commands/list.ts +0 -194
  149. package/src/commands/show.ts +0 -138
  150. package/src/commands/spec.ts +0 -251
  151. package/src/commands/update.ts +0 -129
  152. package/src/commands/upgrade.ts +0 -30
  153. package/src/commands/validate.ts +0 -326
  154. package/src/core/artifact-graph/graph.ts +0 -167
  155. package/src/core/artifact-graph/index.ts +0 -44
  156. package/src/core/artifact-graph/instruction-loader.ts +0 -302
  157. package/src/core/artifact-graph/resolver.ts +0 -226
  158. package/src/core/artifact-graph/schema.ts +0 -124
  159. package/src/core/artifact-graph/state.ts +0 -64
  160. package/src/core/artifact-graph/types.ts +0 -65
  161. package/src/core/completions/command-registry.ts +0 -382
  162. package/src/core/completions/completion-provider.ts +0 -128
  163. package/src/core/completions/generators/bash-generator.ts +0 -191
  164. package/src/core/completions/generators/fish-generator.ts +0 -188
  165. package/src/core/completions/generators/powershell-generator.ts +0 -223
  166. package/src/core/completions/generators/zsh-generator.ts +0 -281
  167. package/src/core/completions/templates/bash-templates.ts +0 -24
  168. package/src/core/completions/templates/fish-templates.ts +0 -40
  169. package/src/core/completions/templates/powershell-templates.ts +0 -25
  170. package/src/core/completions/templates/zsh-templates.ts +0 -36
  171. package/src/core/completions/types.ts +0 -90
  172. package/src/core/config-schema.ts +0 -230
  173. package/src/core/config.ts +0 -181
  174. package/src/core/configurators/slash/antigravity.ts +0 -10
  175. package/src/core/configurators/slash/base.ts +0 -109
  176. package/src/core/configurators/slash/claude.ts +0 -10
  177. package/src/core/configurators/slash/codex.ts +0 -10
  178. package/src/core/configurators/slash/droid.ts +0 -10
  179. package/src/core/configurators/slash/forge.ts +0 -10
  180. package/src/core/configurators/slash/github-copilot.ts +0 -10
  181. package/src/core/configurators/slash/index.ts +0 -10
  182. package/src/core/configurators/slash/kilocode.ts +0 -10
  183. package/src/core/configurators/slash/opencode.ts +0 -10
  184. package/src/core/configurators/slash/registry.ts +0 -51
  185. package/src/core/converters/json-converter.ts +0 -62
  186. package/src/core/global-config.ts +0 -136
  187. package/src/core/parsers/change-parser.ts +0 -234
  188. package/src/core/parsers/markdown-parser.ts +0 -237
  189. package/src/core/parsers/requirement-blocks.ts +0 -234
  190. package/src/core/prompt-templates.ts +0 -3504
  191. package/src/core/schemas/base.schema.ts +0 -20
  192. package/src/core/schemas/change.schema.ts +0 -42
  193. package/src/core/schemas/index.ts +0 -20
  194. package/src/core/schemas/spec.schema.ts +0 -17
  195. package/src/core/skill-discovery.ts +0 -68
  196. package/src/core/specs-apply.ts +0 -483
  197. package/src/core/styles/palette.ts +0 -8
  198. package/src/core/templates/agents-template.ts +0 -459
  199. package/src/core/templates/claude-template.ts +0 -2
  200. package/src/core/templates/index.ts +0 -3
  201. package/src/core/templates/project-template.ts +0 -32
  202. package/src/core/validation/constants.ts +0 -48
  203. package/src/core/validation/types.ts +0 -19
  204. package/src/core/validation/validator.ts +0 -449
  205. package/src/core/view.ts +0 -219
  206. package/src/index.ts +0 -1
  207. package/src/utils/change-metadata.ts +0 -171
  208. package/src/utils/change-utils.ts +0 -131
  209. package/src/utils/file-system.ts +0 -252
  210. package/src/utils/index.ts +0 -12
  211. package/src/utils/interactive.ts +0 -29
  212. package/src/utils/item-discovery.ts +0 -66
  213. package/src/utils/match.ts +0 -26
  214. package/src/utils/shell-detection.ts +0 -62
  215. package/src/utils/task-progress.ts +0 -43
  216. package/tsconfig.json +0 -28
@@ -1,194 +0,0 @@
1
- import { promises as fs } from 'fs';
2
- import path from 'path';
3
- import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
4
- import { readFileSync } from 'fs';
5
- import { join } from 'path';
6
- import { MarkdownParser } from '../core/parsers/markdown-parser.js';
7
-
8
- interface ChangeInfo {
9
- name: string;
10
- completedTasks: number;
11
- totalTasks: number;
12
- lastModified: Date;
13
- }
14
-
15
- interface ListOptions {
16
- sort?: 'recent' | 'name';
17
- json?: boolean;
18
- }
19
-
20
- /**
21
- * Get the most recent modification time of any file in a directory (recursive).
22
- * Falls back to the directory's own mtime if no files are found.
23
- */
24
- async function getLastModified(dirPath: string): Promise<Date> {
25
- let latest: Date | null = null;
26
-
27
- async function walk(dir: string): Promise<void> {
28
- const entries = await fs.readdir(dir, { withFileTypes: true });
29
- for (const entry of entries) {
30
- const fullPath = path.join(dir, entry.name);
31
- if (entry.isDirectory()) {
32
- await walk(fullPath);
33
- } else {
34
- const stat = await fs.stat(fullPath);
35
- if (latest === null || stat.mtime > latest) {
36
- latest = stat.mtime;
37
- }
38
- }
39
- }
40
- }
41
-
42
- await walk(dirPath);
43
-
44
- // If no files found, use the directory's own modification time
45
- if (latest === null) {
46
- const dirStat = await fs.stat(dirPath);
47
- return dirStat.mtime;
48
- }
49
-
50
- return latest;
51
- }
52
-
53
- /**
54
- * Format a date as relative time (e.g., "2 hours ago", "3 days ago")
55
- */
56
- function formatRelativeTime(date: Date): string {
57
- const now = new Date();
58
- const diffMs = now.getTime() - date.getTime();
59
- const diffSecs = Math.floor(diffMs / 1000);
60
- const diffMins = Math.floor(diffSecs / 60);
61
- const diffHours = Math.floor(diffMins / 60);
62
- const diffDays = Math.floor(diffHours / 24);
63
-
64
- if (diffDays > 30) {
65
- return date.toLocaleDateString();
66
- } else if (diffDays > 0) {
67
- return `${diffDays}d ago`;
68
- } else if (diffHours > 0) {
69
- return `${diffHours}h ago`;
70
- } else if (diffMins > 0) {
71
- return `${diffMins}m ago`;
72
- } else {
73
- return 'just now';
74
- }
75
- }
76
-
77
- export class ListCommand {
78
- async execute(targetPath: string = '.', mode: 'changes' | 'specs' = 'changes', options: ListOptions = {}): Promise<void> {
79
- const { sort = 'recent', json = false } = options;
80
-
81
- if (mode === 'changes') {
82
- const changesDir = path.join(targetPath, 'prompter', 'changes');
83
-
84
- // Check if changes directory exists
85
- try {
86
- await fs.access(changesDir);
87
- } catch {
88
- throw new Error("No Prompter changes directory found. Run 'prompter init' first.");
89
- }
90
-
91
- // Get all directories in changes (excluding archive)
92
- const entries = await fs.readdir(changesDir, { withFileTypes: true });
93
- const changeDirs = entries
94
- .filter(entry => entry.isDirectory() && entry.name !== 'archive')
95
- .map(entry => entry.name);
96
-
97
- if (changeDirs.length === 0) {
98
- if (json) {
99
- console.log(JSON.stringify({ changes: [] }));
100
- } else {
101
- console.log('No active changes found.');
102
- }
103
- return;
104
- }
105
-
106
- // Collect information about each change
107
- const changes: ChangeInfo[] = [];
108
-
109
- for (const changeDir of changeDirs) {
110
- const progress = await getTaskProgressForChange(changesDir, changeDir);
111
- const changePath = path.join(changesDir, changeDir);
112
- const lastModified = await getLastModified(changePath);
113
- changes.push({
114
- name: changeDir,
115
- completedTasks: progress.completed,
116
- totalTasks: progress.total,
117
- lastModified
118
- });
119
- }
120
-
121
- // Sort by preference (default: recent first)
122
- if (sort === 'recent') {
123
- changes.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
124
- } else {
125
- changes.sort((a, b) => a.name.localeCompare(b.name));
126
- }
127
-
128
- // JSON output for programmatic use
129
- if (json) {
130
- const jsonOutput = changes.map(c => ({
131
- name: c.name,
132
- completedTasks: c.completedTasks,
133
- totalTasks: c.totalTasks,
134
- lastModified: c.lastModified.toISOString(),
135
- status: c.totalTasks === 0 ? 'no-tasks' : c.completedTasks === c.totalTasks ? 'complete' : 'in-progress'
136
- }));
137
- console.log(JSON.stringify({ changes: jsonOutput }, null, 2));
138
- return;
139
- }
140
-
141
- // Display results
142
- console.log('Changes:');
143
- const padding = ' ';
144
- const nameWidth = Math.max(...changes.map(c => c.name.length));
145
- for (const change of changes) {
146
- const paddedName = change.name.padEnd(nameWidth);
147
- const status = formatTaskStatus({ total: change.totalTasks, completed: change.completedTasks });
148
- const timeAgo = formatRelativeTime(change.lastModified);
149
- console.log(`${padding}${paddedName} ${status.padEnd(12)} ${timeAgo}`);
150
- }
151
- return;
152
- }
153
-
154
- // specs mode
155
- const specsDir = path.join(targetPath, 'prompter', 'specs');
156
- try {
157
- await fs.access(specsDir);
158
- } catch {
159
- console.log('No specs found.');
160
- return;
161
- }
162
-
163
- const entries = await fs.readdir(specsDir, { withFileTypes: true });
164
- const specDirs = entries.filter(e => e.isDirectory()).map(e => e.name);
165
- if (specDirs.length === 0) {
166
- console.log('No specs found.');
167
- return;
168
- }
169
-
170
- type SpecInfo = { id: string; requirementCount: number };
171
- const specs: SpecInfo[] = [];
172
- for (const id of specDirs) {
173
- const specPath = join(specsDir, id, 'spec.md');
174
- try {
175
- const content = readFileSync(specPath, 'utf-8');
176
- const parser = new MarkdownParser(content);
177
- const spec = parser.parseSpec(id);
178
- specs.push({ id, requirementCount: spec.requirements.length });
179
- } catch {
180
- // If spec cannot be read or parsed, include with 0 count
181
- specs.push({ id, requirementCount: 0 });
182
- }
183
- }
184
-
185
- specs.sort((a, b) => a.id.localeCompare(b.id));
186
- console.log('Specs:');
187
- const padding = ' ';
188
- const nameWidth = Math.max(...specs.map(s => s.id.length));
189
- for (const spec of specs) {
190
- const padded = spec.id.padEnd(nameWidth);
191
- console.log(`${padding}${padded} requirements ${spec.requirementCount}`);
192
- }
193
- }
194
- }
@@ -1,138 +0,0 @@
1
- import path from 'path';
2
- import { isInteractive } from '../utils/interactive.js';
3
- import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
4
- import { ChangeCommand } from './change.js';
5
- import { SpecCommand } from './spec.js';
6
- import { nearestMatches } from '../utils/match.js';
7
-
8
- type ItemType = 'change' | 'spec';
9
-
10
- const CHANGE_FLAG_KEYS = new Set(['deltasOnly', 'requirementsOnly']);
11
- const SPEC_FLAG_KEYS = new Set(['requirements', 'scenarios', 'requirement']);
12
-
13
- export class ShowCommand {
14
- async execute(itemName?: string, options: { json?: boolean; type?: string; noInteractive?: boolean; [k: string]: any } = {}): Promise<void> {
15
- const interactive = isInteractive(options);
16
- const typeOverride = this.normalizeType(options.type);
17
-
18
- if (!itemName) {
19
- if (interactive) {
20
- const { select } = await import('@inquirer/prompts');
21
- const type = await select<ItemType>({
22
- message: 'What would you like to show?',
23
- choices: [
24
- { name: 'Change', value: 'change' as const },
25
- { name: 'Spec', value: 'spec' as const },
26
- ],
27
- });
28
- await this.runInteractiveByType(type, options);
29
- return;
30
- }
31
- this.printNonInteractiveHint();
32
- process.exitCode = 1;
33
- return;
34
- }
35
-
36
- await this.showDirect(itemName, { typeOverride, options });
37
- }
38
-
39
- private normalizeType(value?: string): ItemType | undefined {
40
- if (!value) return undefined;
41
- const v = value.toLowerCase();
42
- if (v === 'change' || v === 'spec') return v;
43
- return undefined;
44
- }
45
-
46
- private async runInteractiveByType(type: ItemType, options: { json?: boolean; noInteractive?: boolean; [k: string]: any }): Promise<void> {
47
- const { select } = await import('@inquirer/prompts');
48
- if (type === 'change') {
49
- const changes = await getActiveChangeIds();
50
- if (changes.length === 0) {
51
- console.error('No changes found.');
52
- process.exitCode = 1;
53
- return;
54
- }
55
- const picked = await select<string>({ message: 'Pick a change', choices: changes.map(id => ({ name: id, value: id })) });
56
- const cmd = new ChangeCommand();
57
- await cmd.show(picked, options as any);
58
- return;
59
- }
60
-
61
- const specs = await getSpecIds();
62
- if (specs.length === 0) {
63
- console.error('No specs found.');
64
- process.exitCode = 1;
65
- return;
66
- }
67
- const picked = await select<string>({ message: 'Pick a spec', choices: specs.map(id => ({ name: id, value: id })) });
68
- const cmd = new SpecCommand();
69
- await cmd.show(picked, options as any);
70
- }
71
-
72
- private async showDirect(itemName: string, params: { typeOverride?: ItemType; options: { json?: boolean; [k: string]: any } }): Promise<void> {
73
- // Optimize lookups when type is pre-specified
74
- let isChange = false;
75
- let isSpec = false;
76
- let changes: string[] = [];
77
- let specs: string[] = [];
78
- if (params.typeOverride === 'change') {
79
- changes = await getActiveChangeIds();
80
- isChange = changes.includes(itemName);
81
- } else if (params.typeOverride === 'spec') {
82
- specs = await getSpecIds();
83
- isSpec = specs.includes(itemName);
84
- } else {
85
- [changes, specs] = await Promise.all([getActiveChangeIds(), getSpecIds()]);
86
- isChange = changes.includes(itemName);
87
- isSpec = specs.includes(itemName);
88
- }
89
-
90
- const resolvedType = params.typeOverride ?? (isChange ? 'change' : isSpec ? 'spec' : undefined);
91
-
92
- if (!resolvedType) {
93
- console.error(`Unknown item '${itemName}'`);
94
- const suggestions = nearestMatches(itemName, [...changes, ...specs]);
95
- if (suggestions.length) console.error(`Did you mean: ${suggestions.join(', ')}?`);
96
- process.exitCode = 1;
97
- return;
98
- }
99
-
100
- if (!params.typeOverride && isChange && isSpec) {
101
- console.error(`Ambiguous item '${itemName}' matches both a change and a spec.`);
102
- console.error('Pass --type change|spec, or use: prompter change show / prompter spec show');
103
- process.exitCode = 1;
104
- return;
105
- }
106
-
107
- this.warnIrrelevantFlags(resolvedType, params.options);
108
- if (resolvedType === 'change') {
109
- const cmd = new ChangeCommand();
110
- await cmd.show(itemName, params.options as any);
111
- return;
112
- }
113
- const cmd = new SpecCommand();
114
- await cmd.show(itemName, params.options as any);
115
- }
116
-
117
- private printNonInteractiveHint(): void {
118
- console.error('Nothing to show. Try one of:');
119
- console.error(' prompter show <item>');
120
- console.error(' prompter change show');
121
- console.error(' prompter spec show');
122
- console.error('Or run in an interactive terminal.');
123
- }
124
-
125
- private warnIrrelevantFlags(type: ItemType, options: { [k: string]: any }): boolean {
126
- const irrelevant: string[] = [];
127
- if (type === 'change') {
128
- for (const k of SPEC_FLAG_KEYS) if (k in options) irrelevant.push(k);
129
- } else {
130
- for (const k of CHANGE_FLAG_KEYS) if (k in options) irrelevant.push(k);
131
- }
132
- if (irrelevant.length > 0) {
133
- console.error(`Warning: Ignoring flags not applicable to ${type}: ${irrelevant.join(', ')}`);
134
- return true;
135
- }
136
- return false;
137
- }
138
- }
@@ -1,251 +0,0 @@
1
- import { program } from 'commander';
2
- import { existsSync, readdirSync, readFileSync } from 'fs';
3
- import { join } from 'path';
4
- import { MarkdownParser } from '../core/parsers/markdown-parser.js';
5
- import { Validator } from '../core/validation/validator.js';
6
- import type { Spec } from '../core/schemas/index.js';
7
- import { isInteractive } from '../utils/interactive.js';
8
- import { getSpecIds } from '../utils/item-discovery.js';
9
-
10
- const SPECS_DIR = 'prompter/specs';
11
-
12
- interface ShowOptions {
13
- json?: boolean;
14
- // JSON-only filters (raw-first text has no filters)
15
- requirements?: boolean;
16
- scenarios?: boolean; // --no-scenarios sets this to false (JSON only)
17
- requirement?: string; // JSON only
18
- noInteractive?: boolean;
19
- }
20
-
21
- function parseSpecFromFile(specPath: string, specId: string): Spec {
22
- const content = readFileSync(specPath, 'utf-8');
23
- const parser = new MarkdownParser(content);
24
- return parser.parseSpec(specId);
25
- }
26
-
27
- function validateRequirementIndex(spec: Spec, requirementOpt?: string): number | undefined {
28
- if (!requirementOpt) return undefined;
29
- const index = Number.parseInt(requirementOpt, 10);
30
- if (!Number.isInteger(index) || index < 1 || index > spec.requirements.length) {
31
- throw new Error(`Requirement ${requirementOpt} not found`);
32
- }
33
- return index - 1; // convert to 0-based
34
- }
35
-
36
- function filterSpec(spec: Spec, options: ShowOptions): Spec {
37
- const requirementIndex = validateRequirementIndex(spec, options.requirement);
38
- const includeScenarios = options.scenarios !== false && !options.requirements;
39
-
40
- const filteredRequirements = (requirementIndex !== undefined
41
- ? [spec.requirements[requirementIndex]]
42
- : spec.requirements
43
- ).map(req => ({
44
- text: req.text,
45
- scenarios: includeScenarios ? req.scenarios : [],
46
- }));
47
-
48
- const metadata = spec.metadata ?? { version: '1.0.0', format: 'prompter' as const };
49
-
50
- return {
51
- name: spec.name,
52
- overview: spec.overview,
53
- requirements: filteredRequirements,
54
- metadata,
55
- };
56
- }
57
-
58
- /**
59
- * Print the raw markdown content for a spec file without any formatting.
60
- * Raw-first behavior ensures text mode is a passthrough for deterministic output.
61
- */
62
- function printSpecTextRaw(specPath: string): void {
63
- const content = readFileSync(specPath, 'utf-8');
64
- console.log(content);
65
- }
66
-
67
- export class SpecCommand {
68
- private SPECS_DIR = 'prompter/specs';
69
-
70
- async show(specId?: string, options: ShowOptions = {}): Promise<void> {
71
- if (!specId) {
72
- const canPrompt = isInteractive(options);
73
- const specIds = await getSpecIds();
74
- if (canPrompt && specIds.length > 0) {
75
- const { select } = await import('@inquirer/prompts');
76
- specId = await select({
77
- message: 'Select a spec to show',
78
- choices: specIds.map(id => ({ name: id, value: id })),
79
- });
80
- } else {
81
- throw new Error('Missing required argument <spec-id>');
82
- }
83
- }
84
-
85
- const specPath = join(this.SPECS_DIR, specId, 'spec.md');
86
- if (!existsSync(specPath)) {
87
- throw new Error(`Spec '${specId}' not found at prompter/specs/${specId}/spec.md`);
88
- }
89
-
90
- if (options.json) {
91
- if (options.requirements && options.requirement) {
92
- throw new Error('Options --requirements and --requirement cannot be used together');
93
- }
94
- const parsed = parseSpecFromFile(specPath, specId);
95
- const filtered = filterSpec(parsed, options);
96
- const output = {
97
- id: specId,
98
- title: parsed.name,
99
- overview: parsed.overview,
100
- requirementCount: filtered.requirements.length,
101
- requirements: filtered.requirements,
102
- metadata: parsed.metadata ?? { version: '1.0.0', format: 'prompter' as const },
103
- };
104
- console.log(JSON.stringify(output, null, 2));
105
- return;
106
- }
107
- printSpecTextRaw(specPath);
108
- }
109
- }
110
-
111
- export function registerSpecCommand(rootProgram: typeof program) {
112
- const specCommand = rootProgram
113
- .command('spec')
114
- .description('Manage and view Prompter specifications');
115
-
116
- // Deprecation notice for noun-based commands
117
- specCommand.hook('preAction', () => {
118
- console.error('Warning: The "prompter spec ..." commands are deprecated. Prefer verb-first commands (e.g., "prompter show", "prompter validate --specs").');
119
- });
120
-
121
- specCommand
122
- .command('show [spec-id]')
123
- .description('Display a specific specification')
124
- .option('--json', 'Output as JSON')
125
- .option('--requirements', 'JSON only: Show only requirements (exclude scenarios)')
126
- .option('--no-scenarios', 'JSON only: Exclude scenario content')
127
- .option('-r, --requirement <id>', 'JSON only: Show specific requirement by ID (1-based)')
128
- .option('--no-interactive', 'Disable interactive prompts')
129
- .action(async (specId: string | undefined, options: ShowOptions & { noInteractive?: boolean }) => {
130
- try {
131
- const cmd = new SpecCommand();
132
- await cmd.show(specId, options as any);
133
- } catch (error) {
134
- console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
135
- process.exitCode = 1;
136
- }
137
- });
138
-
139
- specCommand
140
- .command('list')
141
- .description('List all available specifications')
142
- .option('--json', 'Output as JSON')
143
- .option('--long', 'Show id and title with counts')
144
- .action((options: { json?: boolean; long?: boolean }) => {
145
- try {
146
- if (!existsSync(SPECS_DIR)) {
147
- console.log('No items found');
148
- return;
149
- }
150
-
151
- const specs = readdirSync(SPECS_DIR, { withFileTypes: true })
152
- .filter(dirent => dirent.isDirectory())
153
- .map(dirent => {
154
- const specPath = join(SPECS_DIR, dirent.name, 'spec.md');
155
- if (existsSync(specPath)) {
156
- try {
157
- const spec = parseSpecFromFile(specPath, dirent.name);
158
-
159
- return {
160
- id: dirent.name,
161
- title: spec.name,
162
- requirementCount: spec.requirements.length
163
- };
164
- } catch {
165
- return {
166
- id: dirent.name,
167
- title: dirent.name,
168
- requirementCount: 0
169
- };
170
- }
171
- }
172
- return null;
173
- })
174
- .filter((spec): spec is { id: string; title: string; requirementCount: number } => spec !== null)
175
- .sort((a, b) => a.id.localeCompare(b.id));
176
-
177
- if (options.json) {
178
- console.log(JSON.stringify(specs, null, 2));
179
- } else {
180
- if (specs.length === 0) {
181
- console.log('No items found');
182
- return;
183
- }
184
- if (!options.long) {
185
- specs.forEach(spec => console.log(spec.id));
186
- return;
187
- }
188
- specs.forEach(spec => {
189
- console.log(`${spec.id}: ${spec.title} [requirements ${spec.requirementCount}]`);
190
- });
191
- }
192
- } catch (error) {
193
- console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
194
- process.exitCode = 1;
195
- }
196
- });
197
-
198
- specCommand
199
- .command('validate [spec-id]')
200
- .description('Validate a specification structure')
201
- .option('--strict', 'Enable strict validation mode')
202
- .option('--json', 'Output validation report as JSON')
203
- .option('--no-interactive', 'Disable interactive prompts')
204
- .action(async (specId: string | undefined, options: { strict?: boolean; json?: boolean; noInteractive?: boolean }) => {
205
- try {
206
- if (!specId) {
207
- const canPrompt = isInteractive(options);
208
- const specIds = await getSpecIds();
209
- if (canPrompt && specIds.length > 0) {
210
- const { select } = await import('@inquirer/prompts');
211
- specId = await select({
212
- message: 'Select a spec to validate',
213
- choices: specIds.map(id => ({ name: id, value: id })),
214
- });
215
- } else {
216
- throw new Error('Missing required argument <spec-id>');
217
- }
218
- }
219
-
220
- const specPath = join(SPECS_DIR, specId, 'spec.md');
221
-
222
- if (!existsSync(specPath)) {
223
- throw new Error(`Spec '${specId}' not found at prompter/specs/${specId}/spec.md`);
224
- }
225
-
226
- const validator = new Validator(options.strict);
227
- const report = await validator.validateSpec(specPath);
228
-
229
- if (options.json) {
230
- console.log(JSON.stringify(report, null, 2));
231
- } else {
232
- if (report.valid) {
233
- console.log(`Specification '${specId}' is valid`);
234
- } else {
235
- console.error(`Specification '${specId}' has issues`);
236
- report.issues.forEach(issue => {
237
- const label = issue.level === 'ERROR' ? 'ERROR' : issue.level;
238
- const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
239
- console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
240
- });
241
- }
242
- }
243
- process.exitCode = report.valid ? 0 : 1;
244
- } catch (error) {
245
- console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
246
- process.exitCode = 1;
247
- }
248
- });
249
-
250
- return specCommand;
251
- }