@dedesfr/prompter 0.9.0 → 1.1.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 (225) hide show
  1. package/CHANGELOG.md +35 -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 +35 -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 +0 -2
  17. package/dist/commands/update.d.ts.map +1 -1
  18. package/dist/commands/update.js +19 -48
  19. package/dist/commands/update.js.map +1 -1
  20. package/dist/commands/whoami.d.ts +4 -0
  21. package/dist/commands/whoami.d.ts.map +1 -0
  22. package/dist/commands/whoami.js +42 -0
  23. package/dist/commands/whoami.js.map +1 -0
  24. package/dist/core/auth-store.d.ts +10 -0
  25. package/dist/core/auth-store.d.ts.map +1 -0
  26. package/dist/core/auth-store.js +39 -0
  27. package/dist/core/auth-store.js.map +1 -0
  28. package/dist/core/config.d.ts +0 -7
  29. package/dist/core/config.d.ts.map +1 -1
  30. package/dist/core/config.js +0 -128
  31. package/dist/core/config.js.map +1 -1
  32. package/dist/core/registry.d.ts +18 -0
  33. package/dist/core/registry.d.ts.map +1 -0
  34. package/dist/core/registry.js +94 -0
  35. package/dist/core/registry.js.map +1 -0
  36. package/package.json +7 -1
  37. package/AGENTS.md +0 -123
  38. package/CLAUDE.md +0 -17
  39. package/build.js +0 -20
  40. package/convex-setup.md +0 -403
  41. package/dist/core/prompt-templates.d.ts +0 -23
  42. package/dist/core/prompt-templates.d.ts.map +0 -1
  43. package/dist/core/prompt-templates.js +0 -3485
  44. package/dist/core/prompt-templates.js.map +0 -1
  45. package/prompt/ai-humanizer.md +0 -45
  46. package/prompt/api-contract-generator.md +0 -234
  47. package/prompt/apply.md +0 -17
  48. package/prompt/archive.md +0 -21
  49. package/prompt/design-system.md +0 -210
  50. package/prompt/document-explainer.md +0 -149
  51. package/prompt/epic-generator.md +0 -198
  52. package/prompt/epic-single.md +0 -47
  53. package/prompt/erd-generator.md +0 -130
  54. package/prompt/fsd-generator.md +0 -157
  55. package/prompt/prd-agent-generator.md +0 -147
  56. package/prompt/prd-generator.md +0 -195
  57. package/prompt/product-brief.md +0 -289
  58. package/prompt/proposal.md +0 -22
  59. package/prompt/qa-test-scenario.md +0 -133
  60. package/prompt/skill-creator.md +0 -350
  61. package/prompt/story-generator.md +0 -278
  62. package/prompt/story-single.md +0 -70
  63. package/prompt/tdd-generator.md +0 -294
  64. package/prompt/tdd-lite-generator.md +0 -224
  65. package/prompt/wireframe-generator.md +0 -219
  66. package/skills/ai-context-generator/SKILL.md +0 -54
  67. package/skills/ai-context-generator/references/AGENTS.template.md +0 -83
  68. package/skills/ai-context-generator/references/CLAUDE.template.md +0 -39
  69. package/skills/ai-context-generator/references/behavioral-guidelines.md +0 -71
  70. package/skills/ai-context-generator/references/discovery-checklist.md +0 -40
  71. package/skills/ai-context-generator/references/examples/AGENTS.good.md +0 -103
  72. package/skills/ai-context-generator/references/extraction-checklist.md +0 -23
  73. package/skills/ai-context-generator/references/overlays/laravel.md +0 -44
  74. package/skills/ai-humanizer/SKILL.md +0 -50
  75. package/skills/api-contract-generator/SKILL.md +0 -243
  76. package/skills/apply/SKILL.md +0 -23
  77. package/skills/archive/SKILL.md +0 -27
  78. package/skills/cerebro/SKILL.md +0 -187
  79. package/skills/cerebro/references/agents.md +0 -213
  80. package/skills/code-review/SKILL.md +0 -373
  81. package/skills/code-review/assets/report-template-agent.md +0 -212
  82. package/skills/code-review/assets/report-template-compact.md +0 -81
  83. package/skills/code-review/assets/report-template-full.md +0 -264
  84. package/skills/code-review/assets/report-template-human.md +0 -168
  85. package/skills/code-review/references/universal-patterns.md +0 -495
  86. package/skills/design-md/README.md +0 -34
  87. package/skills/design-md/SKILL.md +0 -172
  88. package/skills/design-md/examples/DESIGN.md +0 -154
  89. package/skills/design-system/SKILL.md +0 -216
  90. package/skills/design-system-generator/SKILL.md +0 -324
  91. package/skills/design-system-generator/assets/design-system-template.md +0 -348
  92. package/skills/design-system-generator/references/extraction-patterns.md +0 -321
  93. package/skills/doc-builder/SKILL.md +0 -115
  94. package/skills/doc-builder/references/ui-patterns.md +0 -394
  95. package/skills/document-explainer/SKILL.md +0 -155
  96. package/skills/document-translator/SKILL.md +0 -58
  97. package/skills/enhance/SKILL.md +0 -47
  98. package/skills/enhance-prompt/README.md +0 -34
  99. package/skills/enhance-prompt/SKILL.md +0 -204
  100. package/skills/enhance-prompt/references/KEYWORDS.md +0 -114
  101. package/skills/epic-generator/SKILL.md +0 -204
  102. package/skills/epic-single/SKILL.md +0 -63
  103. package/skills/erd-generator/SKILL.md +0 -138
  104. package/skills/feature-planner/SKILL.md +0 -305
  105. package/skills/feature-planner/assets/implementation-plan-template.md +0 -85
  106. package/skills/frontend-design/LICENSE.txt +0 -177
  107. package/skills/frontend-design/SKILL.md +0 -42
  108. package/skills/fsd-generator/SKILL.md +0 -163
  109. package/skills/gamma-builder/SKILL.md +0 -134
  110. package/skills/laravel-code-review/SKILL.md +0 -383
  111. package/skills/laravel-code-review/assets/report-template-agent.md +0 -195
  112. package/skills/laravel-code-review/assets/report-template-compact.md +0 -79
  113. package/skills/laravel-code-review/assets/report-template-full.md +0 -253
  114. package/skills/laravel-code-review/assets/report-template-human.md +0 -159
  115. package/skills/laravel-code-review/references/laravel-patterns.md +0 -571
  116. package/skills/laravel-code-review/references/php84-features.md +0 -442
  117. package/skills/mcp-builder/LICENSE.txt +0 -202
  118. package/skills/mcp-builder/SKILL.md +0 -236
  119. package/skills/mcp-builder/reference/evaluation.md +0 -602
  120. package/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  121. package/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  122. package/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  123. package/skills/mcp-builder/scripts/connections.py +0 -151
  124. package/skills/mcp-builder/scripts/evaluation.py +0 -373
  125. package/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  126. package/skills/mcp-builder/scripts/requirements.txt +0 -2
  127. package/skills/meeting-notes/SKILL.md +0 -159
  128. package/skills/meeting-notes/evals/evals.json +0 -23
  129. package/skills/prd-agent-generator/SKILL.md +0 -132
  130. package/skills/prd-generator/SKILL.md +0 -211
  131. package/skills/product-brief/SKILL.md +0 -141
  132. package/skills/project-orchestrator/SKILL.md +0 -487
  133. package/skills/project-orchestrator/assets/caddy-vps-setup.md +0 -180
  134. package/skills/project-orchestrator/assets/plan-summary-template.md +0 -159
  135. package/skills/prompter-specs/SKILL.md +0 -115
  136. package/skills/prompter-workflow/SKILL.md +0 -166
  137. package/skills/prompter-workflow/evals/evals.json +0 -89
  138. package/skills/proposal/SKILL.md +0 -28
  139. package/skills/qa-test-scenario/SKILL.md +0 -149
  140. package/skills/skill-creator/SKILL.md +0 -173
  141. package/skills/sph-generator/SKILL.md +0 -488
  142. package/skills/story-generator/SKILL.md +0 -285
  143. package/skills/story-single/SKILL.md +0 -86
  144. package/skills/tdd-generator/SKILL.md +0 -300
  145. package/skills/tdd-lite-generator/SKILL.md +0 -230
  146. package/skills/ui-ux-pro/SKILL.md +0 -199
  147. package/skills/ui-ux-pro/assets/design-spec-template.md +0 -173
  148. package/skills/ui-ux-pro/references/component-patterns.md +0 -255
  149. package/skills/ui-ux-pro/references/design-principles.md +0 -167
  150. package/skills/wireframe-generator/SKILL.md +0 -227
  151. package/src/cli/index.ts +0 -223
  152. package/src/commands/archive.ts +0 -302
  153. package/src/commands/change.ts +0 -292
  154. package/src/commands/config.ts +0 -233
  155. package/src/commands/guide.ts +0 -50
  156. package/src/commands/init.ts +0 -597
  157. package/src/commands/list.ts +0 -194
  158. package/src/commands/show.ts +0 -138
  159. package/src/commands/spec.ts +0 -251
  160. package/src/commands/update.ts +0 -129
  161. package/src/commands/upgrade.ts +0 -30
  162. package/src/commands/validate.ts +0 -326
  163. package/src/core/artifact-graph/graph.ts +0 -167
  164. package/src/core/artifact-graph/index.ts +0 -44
  165. package/src/core/artifact-graph/instruction-loader.ts +0 -302
  166. package/src/core/artifact-graph/resolver.ts +0 -226
  167. package/src/core/artifact-graph/schema.ts +0 -124
  168. package/src/core/artifact-graph/state.ts +0 -64
  169. package/src/core/artifact-graph/types.ts +0 -65
  170. package/src/core/completions/command-registry.ts +0 -382
  171. package/src/core/completions/completion-provider.ts +0 -128
  172. package/src/core/completions/generators/bash-generator.ts +0 -191
  173. package/src/core/completions/generators/fish-generator.ts +0 -188
  174. package/src/core/completions/generators/powershell-generator.ts +0 -223
  175. package/src/core/completions/generators/zsh-generator.ts +0 -281
  176. package/src/core/completions/templates/bash-templates.ts +0 -24
  177. package/src/core/completions/templates/fish-templates.ts +0 -40
  178. package/src/core/completions/templates/powershell-templates.ts +0 -25
  179. package/src/core/completions/templates/zsh-templates.ts +0 -36
  180. package/src/core/completions/types.ts +0 -90
  181. package/src/core/config-schema.ts +0 -230
  182. package/src/core/config.ts +0 -181
  183. package/src/core/configurators/slash/antigravity.ts +0 -10
  184. package/src/core/configurators/slash/base.ts +0 -109
  185. package/src/core/configurators/slash/claude.ts +0 -10
  186. package/src/core/configurators/slash/codex.ts +0 -10
  187. package/src/core/configurators/slash/droid.ts +0 -10
  188. package/src/core/configurators/slash/forge.ts +0 -10
  189. package/src/core/configurators/slash/github-copilot.ts +0 -10
  190. package/src/core/configurators/slash/index.ts +0 -10
  191. package/src/core/configurators/slash/kilocode.ts +0 -10
  192. package/src/core/configurators/slash/opencode.ts +0 -10
  193. package/src/core/configurators/slash/registry.ts +0 -51
  194. package/src/core/converters/json-converter.ts +0 -62
  195. package/src/core/global-config.ts +0 -136
  196. package/src/core/parsers/change-parser.ts +0 -234
  197. package/src/core/parsers/markdown-parser.ts +0 -237
  198. package/src/core/parsers/requirement-blocks.ts +0 -234
  199. package/src/core/prompt-templates.ts +0 -3504
  200. package/src/core/schemas/base.schema.ts +0 -20
  201. package/src/core/schemas/change.schema.ts +0 -42
  202. package/src/core/schemas/index.ts +0 -20
  203. package/src/core/schemas/spec.schema.ts +0 -17
  204. package/src/core/skill-discovery.ts +0 -68
  205. package/src/core/specs-apply.ts +0 -483
  206. package/src/core/styles/palette.ts +0 -8
  207. package/src/core/templates/agents-template.ts +0 -459
  208. package/src/core/templates/claude-template.ts +0 -2
  209. package/src/core/templates/index.ts +0 -3
  210. package/src/core/templates/project-template.ts +0 -32
  211. package/src/core/validation/constants.ts +0 -48
  212. package/src/core/validation/types.ts +0 -19
  213. package/src/core/validation/validator.ts +0 -449
  214. package/src/core/view.ts +0 -219
  215. package/src/index.ts +0 -1
  216. package/src/utils/change-metadata.ts +0 -171
  217. package/src/utils/change-utils.ts +0 -131
  218. package/src/utils/file-system.ts +0 -252
  219. package/src/utils/index.ts +0 -12
  220. package/src/utils/interactive.ts +0 -29
  221. package/src/utils/item-discovery.ts +0 -66
  222. package/src/utils/match.ts +0 -26
  223. package/src/utils/shell-detection.ts +0 -62
  224. package/src/utils/task-progress.ts +0 -43
  225. 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
- }