@dedesfr/prompter 0.8.23 → 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 (247) hide show
  1. package/CHANGELOG.md +70 -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 +1 -7
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +60 -299
  8. package/dist/commands/init.js.map +1 -1
  9. package/dist/commands/login.d.ts +4 -0
  10. package/dist/commands/login.d.ts.map +1 -0
  11. package/dist/commands/login.js +56 -0
  12. package/dist/commands/login.js.map +1 -0
  13. package/dist/commands/logout.d.ts +4 -0
  14. package/dist/commands/logout.d.ts.map +1 -0
  15. package/dist/commands/logout.js +14 -0
  16. package/dist/commands/logout.js.map +1 -0
  17. package/dist/commands/update.d.ts.map +1 -1
  18. package/dist/commands/update.js +31 -41
  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/configurators/slash/antigravity.d.ts +2 -5
  29. package/dist/core/configurators/slash/antigravity.d.ts.map +1 -1
  30. package/dist/core/configurators/slash/antigravity.js +2 -57
  31. package/dist/core/configurators/slash/antigravity.js.map +1 -1
  32. package/dist/core/configurators/slash/base.d.ts +6 -18
  33. package/dist/core/configurators/slash/base.d.ts.map +1 -1
  34. package/dist/core/configurators/slash/base.js +8 -77
  35. package/dist/core/configurators/slash/base.js.map +1 -1
  36. package/dist/core/configurators/slash/claude.d.ts +2 -5
  37. package/dist/core/configurators/slash/claude.d.ts.map +1 -1
  38. package/dist/core/configurators/slash/claude.js +2 -57
  39. package/dist/core/configurators/slash/claude.js.map +1 -1
  40. package/dist/core/configurators/slash/codex.d.ts +2 -5
  41. package/dist/core/configurators/slash/codex.d.ts.map +1 -1
  42. package/dist/core/configurators/slash/codex.js +2 -57
  43. package/dist/core/configurators/slash/codex.js.map +1 -1
  44. package/dist/core/configurators/slash/droid.d.ts +2 -5
  45. package/dist/core/configurators/slash/droid.d.ts.map +1 -1
  46. package/dist/core/configurators/slash/droid.js +2 -32
  47. package/dist/core/configurators/slash/droid.js.map +1 -1
  48. package/dist/core/configurators/slash/forge.d.ts +2 -5
  49. package/dist/core/configurators/slash/forge.d.ts.map +1 -1
  50. package/dist/core/configurators/slash/forge.js +2 -32
  51. package/dist/core/configurators/slash/forge.js.map +1 -1
  52. package/dist/core/configurators/slash/github-copilot.d.ts +2 -7
  53. package/dist/core/configurators/slash/github-copilot.d.ts.map +1 -1
  54. package/dist/core/configurators/slash/github-copilot.js +2 -96
  55. package/dist/core/configurators/slash/github-copilot.js.map +1 -1
  56. package/dist/core/configurators/slash/index.d.ts +1 -1
  57. package/dist/core/configurators/slash/index.d.ts.map +1 -1
  58. package/dist/core/configurators/slash/index.js +1 -1
  59. package/dist/core/configurators/slash/index.js.map +1 -1
  60. package/dist/core/configurators/slash/kilocode.d.ts +2 -5
  61. package/dist/core/configurators/slash/kilocode.d.ts.map +1 -1
  62. package/dist/core/configurators/slash/kilocode.js +2 -57
  63. package/dist/core/configurators/slash/kilocode.js.map +1 -1
  64. package/dist/core/configurators/slash/opencode.d.ts +2 -5
  65. package/dist/core/configurators/slash/opencode.d.ts.map +1 -1
  66. package/dist/core/configurators/slash/opencode.js +2 -57
  67. package/dist/core/configurators/slash/opencode.js.map +1 -1
  68. package/dist/core/configurators/slash/registry.d.ts +4 -4
  69. package/dist/core/configurators/slash/registry.d.ts.map +1 -1
  70. package/dist/core/configurators/slash/registry.js.map +1 -1
  71. package/dist/core/registry.d.ts +18 -0
  72. package/dist/core/registry.d.ts.map +1 -0
  73. package/dist/core/registry.js +94 -0
  74. package/dist/core/registry.js.map +1 -0
  75. package/dist/core/templates/index.d.ts +0 -1
  76. package/dist/core/templates/index.d.ts.map +1 -1
  77. package/dist/core/templates/index.js +0 -1
  78. package/dist/core/templates/index.js.map +1 -1
  79. package/package.json +7 -1
  80. package/AGENTS.md +0 -123
  81. package/CLAUDE.md +0 -17
  82. package/build.js +0 -20
  83. package/convex-setup.md +0 -403
  84. package/dist/core/templates/slash-command-templates.d.ts +0 -7
  85. package/dist/core/templates/slash-command-templates.d.ts.map +0 -1
  86. package/dist/core/templates/slash-command-templates.js +0 -1041
  87. package/dist/core/templates/slash-command-templates.js.map +0 -1
  88. package/prompt/ai-humanizer.md +0 -45
  89. package/prompt/api-contract-generator.md +0 -234
  90. package/prompt/apply.md +0 -17
  91. package/prompt/archive.md +0 -21
  92. package/prompt/design-system.md +0 -210
  93. package/prompt/document-explainer.md +0 -149
  94. package/prompt/epic-generator.md +0 -198
  95. package/prompt/epic-single.md +0 -47
  96. package/prompt/erd-generator.md +0 -130
  97. package/prompt/fsd-generator.md +0 -157
  98. package/prompt/prd-agent-generator.md +0 -147
  99. package/prompt/prd-generator.md +0 -195
  100. package/prompt/product-brief.md +0 -289
  101. package/prompt/proposal.md +0 -22
  102. package/prompt/qa-test-scenario.md +0 -133
  103. package/prompt/skill-creator.md +0 -350
  104. package/prompt/story-generator.md +0 -278
  105. package/prompt/story-single.md +0 -70
  106. package/prompt/tdd-generator.md +0 -294
  107. package/prompt/tdd-lite-generator.md +0 -224
  108. package/prompt/wireframe-generator.md +0 -219
  109. package/skills/ai-context-generator/SKILL.md +0 -54
  110. package/skills/ai-context-generator/references/AGENTS.template.md +0 -83
  111. package/skills/ai-context-generator/references/CLAUDE.template.md +0 -39
  112. package/skills/ai-context-generator/references/behavioral-guidelines.md +0 -71
  113. package/skills/ai-context-generator/references/discovery-checklist.md +0 -40
  114. package/skills/ai-context-generator/references/examples/AGENTS.good.md +0 -103
  115. package/skills/ai-context-generator/references/extraction-checklist.md +0 -23
  116. package/skills/ai-context-generator/references/overlays/laravel.md +0 -44
  117. package/skills/cerebro/SKILL.md +0 -187
  118. package/skills/cerebro/references/agents.md +0 -213
  119. package/skills/code-review/SKILL.md +0 -373
  120. package/skills/code-review/assets/report-template-agent.md +0 -212
  121. package/skills/code-review/assets/report-template-compact.md +0 -81
  122. package/skills/code-review/assets/report-template-full.md +0 -264
  123. package/skills/code-review/assets/report-template-human.md +0 -168
  124. package/skills/code-review/references/universal-patterns.md +0 -495
  125. package/skills/design-md/README.md +0 -34
  126. package/skills/design-md/SKILL.md +0 -172
  127. package/skills/design-md/examples/DESIGN.md +0 -154
  128. package/skills/design-system-generator/SKILL.md +0 -324
  129. package/skills/design-system-generator/assets/design-system-template.md +0 -348
  130. package/skills/design-system-generator/references/extraction-patterns.md +0 -321
  131. package/skills/doc-builder/SKILL.md +0 -115
  132. package/skills/doc-builder/references/ui-patterns.md +0 -394
  133. package/skills/document-translator/SKILL.md +0 -58
  134. package/skills/enhance-prompt/README.md +0 -34
  135. package/skills/enhance-prompt/SKILL.md +0 -204
  136. package/skills/enhance-prompt/references/KEYWORDS.md +0 -114
  137. package/skills/feature-planner/SKILL.md +0 -305
  138. package/skills/feature-planner/assets/implementation-plan-template.md +0 -85
  139. package/skills/frontend-design/LICENSE.txt +0 -177
  140. package/skills/frontend-design/SKILL.md +0 -42
  141. package/skills/gamma-builder/SKILL.md +0 -134
  142. package/skills/laravel-code-review/SKILL.md +0 -383
  143. package/skills/laravel-code-review/assets/report-template-agent.md +0 -195
  144. package/skills/laravel-code-review/assets/report-template-compact.md +0 -79
  145. package/skills/laravel-code-review/assets/report-template-full.md +0 -253
  146. package/skills/laravel-code-review/assets/report-template-human.md +0 -159
  147. package/skills/laravel-code-review/references/laravel-patterns.md +0 -571
  148. package/skills/laravel-code-review/references/php84-features.md +0 -442
  149. package/skills/mcp-builder/LICENSE.txt +0 -202
  150. package/skills/mcp-builder/SKILL.md +0 -236
  151. package/skills/mcp-builder/reference/evaluation.md +0 -602
  152. package/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  153. package/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  154. package/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  155. package/skills/mcp-builder/scripts/connections.py +0 -151
  156. package/skills/mcp-builder/scripts/evaluation.py +0 -373
  157. package/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  158. package/skills/mcp-builder/scripts/requirements.txt +0 -2
  159. package/skills/meeting-notes/SKILL.md +0 -159
  160. package/skills/meeting-notes/evals/evals.json +0 -23
  161. package/skills/project-orchestrator/SKILL.md +0 -487
  162. package/skills/project-orchestrator/assets/caddy-vps-setup.md +0 -180
  163. package/skills/project-orchestrator/assets/plan-summary-template.md +0 -159
  164. package/skills/prompter-specs/SKILL.md +0 -115
  165. package/skills/prompter-workflow/SKILL.md +0 -166
  166. package/skills/prompter-workflow/evals/evals.json +0 -89
  167. package/skills/sph-generator/SKILL.md +0 -488
  168. package/skills/ui-ux-pro/SKILL.md +0 -199
  169. package/skills/ui-ux-pro/assets/design-spec-template.md +0 -173
  170. package/skills/ui-ux-pro/references/component-patterns.md +0 -255
  171. package/skills/ui-ux-pro/references/design-principles.md +0 -167
  172. package/src/cli/index.ts +0 -223
  173. package/src/commands/archive.ts +0 -302
  174. package/src/commands/change.ts +0 -292
  175. package/src/commands/config.ts +0 -233
  176. package/src/commands/guide.ts +0 -50
  177. package/src/commands/init.ts +0 -899
  178. package/src/commands/list.ts +0 -194
  179. package/src/commands/show.ts +0 -138
  180. package/src/commands/spec.ts +0 -251
  181. package/src/commands/update.ts +0 -156
  182. package/src/commands/upgrade.ts +0 -30
  183. package/src/commands/validate.ts +0 -326
  184. package/src/core/artifact-graph/graph.ts +0 -167
  185. package/src/core/artifact-graph/index.ts +0 -44
  186. package/src/core/artifact-graph/instruction-loader.ts +0 -302
  187. package/src/core/artifact-graph/resolver.ts +0 -226
  188. package/src/core/artifact-graph/schema.ts +0 -124
  189. package/src/core/artifact-graph/state.ts +0 -64
  190. package/src/core/artifact-graph/types.ts +0 -65
  191. package/src/core/completions/command-registry.ts +0 -382
  192. package/src/core/completions/completion-provider.ts +0 -128
  193. package/src/core/completions/generators/bash-generator.ts +0 -191
  194. package/src/core/completions/generators/fish-generator.ts +0 -188
  195. package/src/core/completions/generators/powershell-generator.ts +0 -223
  196. package/src/core/completions/generators/zsh-generator.ts +0 -281
  197. package/src/core/completions/templates/bash-templates.ts +0 -24
  198. package/src/core/completions/templates/fish-templates.ts +0 -40
  199. package/src/core/completions/templates/powershell-templates.ts +0 -25
  200. package/src/core/completions/templates/zsh-templates.ts +0 -36
  201. package/src/core/completions/types.ts +0 -90
  202. package/src/core/config-schema.ts +0 -230
  203. package/src/core/config.ts +0 -181
  204. package/src/core/configurators/slash/antigravity.ts +0 -70
  205. package/src/core/configurators/slash/base.ts +0 -203
  206. package/src/core/configurators/slash/claude.ts +0 -70
  207. package/src/core/configurators/slash/codex.ts +0 -70
  208. package/src/core/configurators/slash/droid.ts +0 -44
  209. package/src/core/configurators/slash/forge.ts +0 -44
  210. package/src/core/configurators/slash/github-copilot.ts +0 -114
  211. package/src/core/configurators/slash/index.ts +0 -10
  212. package/src/core/configurators/slash/kilocode.ts +0 -70
  213. package/src/core/configurators/slash/opencode.ts +0 -70
  214. package/src/core/configurators/slash/registry.ts +0 -51
  215. package/src/core/converters/json-converter.ts +0 -62
  216. package/src/core/global-config.ts +0 -136
  217. package/src/core/parsers/change-parser.ts +0 -234
  218. package/src/core/parsers/markdown-parser.ts +0 -237
  219. package/src/core/parsers/requirement-blocks.ts +0 -234
  220. package/src/core/prompt-templates.ts +0 -3504
  221. package/src/core/schemas/base.schema.ts +0 -20
  222. package/src/core/schemas/change.schema.ts +0 -42
  223. package/src/core/schemas/index.ts +0 -20
  224. package/src/core/schemas/spec.schema.ts +0 -17
  225. package/src/core/skill-discovery.ts +0 -68
  226. package/src/core/specs-apply.ts +0 -483
  227. package/src/core/styles/palette.ts +0 -8
  228. package/src/core/templates/agents-template.ts +0 -459
  229. package/src/core/templates/claude-template.ts +0 -2
  230. package/src/core/templates/index.ts +0 -4
  231. package/src/core/templates/project-template.ts +0 -32
  232. package/src/core/templates/slash-command-templates.ts +0 -1068
  233. package/src/core/validation/constants.ts +0 -48
  234. package/src/core/validation/types.ts +0 -19
  235. package/src/core/validation/validator.ts +0 -449
  236. package/src/core/view.ts +0 -219
  237. package/src/index.ts +0 -1
  238. package/src/utils/change-metadata.ts +0 -171
  239. package/src/utils/change-utils.ts +0 -131
  240. package/src/utils/file-system.ts +0 -252
  241. package/src/utils/index.ts +0 -12
  242. package/src/utils/interactive.ts +0 -29
  243. package/src/utils/item-discovery.ts +0 -66
  244. package/src/utils/match.ts +0 -26
  245. package/src/utils/shell-detection.ts +0 -62
  246. package/src/utils/task-progress.ts +0 -43
  247. package/tsconfig.json +0 -28
@@ -1,156 +0,0 @@
1
- import chalk from 'chalk';
2
- import { promises as fs } from 'fs';
3
- import path from 'path';
4
- import { PrompterConfig, AVAILABLE_PROMPTS, PROMPTER_DIR } from '../core/config.js';
5
- import { registry } from '../core/configurators/slash/index.js';
6
- import { PROMPT_TEMPLATES } from '../core/prompt-templates.js';
7
- import { discoverSkills } from '../core/skill-discovery.js';
8
-
9
- export class UpdateCommand {
10
- async execute(): Promise<void> {
11
- const projectPath = process.cwd();
12
-
13
- console.log(chalk.blue('\n🔄 Updating Prompter workflow files...\n'));
14
-
15
- // Check if initialized
16
- if (!await PrompterConfig.prompterDirExists(projectPath)) {
17
- console.log(chalk.red('❌ Prompter is not initialized in this project.'));
18
- console.log(chalk.gray(' Run `prompter init` first.\n'));
19
- process.exitCode = 1;
20
- return;
21
- }
22
-
23
- // Detect configured tools
24
- const configuredTools = await this.detectConfiguredTools(projectPath);
25
-
26
- if (configuredTools.length === 0) {
27
- console.log(chalk.yellow('⚠️ No configured tools found.'));
28
- console.log(chalk.gray(' Run `prompter init` to configure tools.\n'));
29
- process.exitCode = 1;
30
- return;
31
- }
32
-
33
- let updatedCount = 0;
34
-
35
- // Update existing workflow files for configured tools only
36
- for (const toolId of configuredTools) {
37
- const configurator = registry.get(toolId);
38
- if (!configurator) continue;
39
-
40
- try {
41
- // Only update existing files, don't create new ones
42
- const updatedFiles = await configurator.updateExisting(projectPath);
43
- for (const file of updatedFiles) {
44
- console.log(chalk.green('✓') + ` Updated ${chalk.cyan(file)}`);
45
- updatedCount++;
46
- }
47
- } catch (error) {
48
- console.log(chalk.red('✗') + ` Failed to update ${configurator.toolId}: ${error}`);
49
- }
50
- }
51
-
52
- // Update existing prompts in prompter/core/
53
- const prompterPath = path.join(projectPath, PROMPTER_DIR);
54
- const updatedCorePrompts = await this.updateCorePrompts(prompterPath);
55
- for (const promptName of updatedCorePrompts) {
56
- console.log(chalk.green('✓') + ` Updated ${chalk.cyan(`${PROMPTER_DIR}/core/${promptName}`)}`);
57
- updatedCount++;
58
- }
59
-
60
- // Update existing skill workflow files
61
- const skillsDir = path.join(projectPath, 'skills');
62
- const availableSkills = await discoverSkills(skillsDir);
63
-
64
- if (availableSkills.length > 0) {
65
- for (const toolId of configuredTools) {
66
- const configurator = registry.get(toolId);
67
- if (!configurator) continue;
68
-
69
- try {
70
- const updatedSkillFiles = await configurator.updateExistingSkills(projectPath, availableSkills);
71
- for (const file of updatedSkillFiles) {
72
- console.log(chalk.green('✓') + ` Updated ${chalk.cyan(file)}`);
73
- updatedCount++;
74
- }
75
- } catch (error) {
76
- console.log(chalk.red('✗') + ` Failed to update skills for ${configurator.toolId}: ${error}`);
77
- }
78
- }
79
- }
80
-
81
- if (updatedCount === 0) {
82
- console.log(chalk.yellow('⚠️ No workflow files found to update.'));
83
- console.log(chalk.gray(' Run `prompter init` to create them.\n'));
84
- } else {
85
- console.log(chalk.green(`\n✅ ${updatedCount} file(s) updated.\n`));
86
- }
87
- }
88
-
89
- private async fileExists(filePath: string): Promise<boolean> {
90
- try {
91
- await fs.access(filePath);
92
- return true;
93
- } catch {
94
- return false;
95
- }
96
- }
97
-
98
- private async detectConfiguredTools(projectPath: string): Promise<string[]> {
99
- const configuredTools: string[] = [];
100
- const allConfigurators = registry.getAll();
101
-
102
- for (const configurator of allConfigurators) {
103
- const targets = configurator.getTargets();
104
- let hasFiles = false;
105
-
106
- for (const target of targets) {
107
- const filePath = path.join(projectPath, target.path);
108
- if (await this.fileExists(filePath)) {
109
- hasFiles = true;
110
- break;
111
- }
112
- }
113
-
114
- if (hasFiles) {
115
- configuredTools.push(configurator.toolId);
116
- }
117
- }
118
-
119
- return configuredTools;
120
- }
121
-
122
- private async updateCorePrompts(prompterPath: string): Promise<string[]> {
123
- const updatedPrompts: string[] = [];
124
- const corePath = path.join(prompterPath, 'core');
125
-
126
- // Check if core directory exists
127
- if (!await this.fileExists(corePath)) {
128
- return updatedPrompts;
129
- }
130
-
131
- // Update each existing prompt file
132
- for (const prompt of AVAILABLE_PROMPTS) {
133
- const promptFilePath = path.join(corePath, prompt.sourceFile);
134
-
135
- // Only update if file exists
136
- if (await this.fileExists(promptFilePath)) {
137
- try {
138
- const content = PROMPT_TEMPLATES[prompt.value];
139
-
140
- if (!content) {
141
- console.log(chalk.yellow(` Warning: Template not found for ${prompt.name}`));
142
- continue;
143
- }
144
-
145
- // Update the prompt file
146
- await fs.writeFile(promptFilePath, content, 'utf-8');
147
- updatedPrompts.push(prompt.sourceFile);
148
- } catch (error) {
149
- console.log(chalk.red(` Error updating ${prompt.name}: ${error}`));
150
- }
151
- }
152
- }
153
-
154
- return updatedPrompts;
155
- }
156
- }
@@ -1,30 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import chalk from 'chalk';
3
-
4
- export class UpgradeCommand {
5
- async execute(): Promise<void> {
6
- console.log(chalk.cyan('\n🔄 Upgrading Prompter to the latest version...\n'));
7
-
8
- const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
9
-
10
- const upgrade = spawn(npmCommand, ['install', '-g', '@dedesfr/prompter@latest'], {
11
- stdio: 'inherit',
12
- shell: true
13
- });
14
-
15
- upgrade.on('close', (code) => {
16
- if (code === 0) {
17
- console.log(chalk.green('\n✓ Prompter has been upgraded successfully!\n'));
18
- } else {
19
- console.error(chalk.red(`\n✗ Upgrade failed with exit code ${code}\n`));
20
- process.exit(code || 1);
21
- }
22
- });
23
-
24
- upgrade.on('error', (error) => {
25
- console.error(chalk.red('\n✗ Failed to run upgrade command:'), error.message);
26
- console.log(chalk.yellow('\nPlease run manually: npm install -g @dedesfr/prompter@latest\n'));
27
- process.exit(1);
28
- });
29
- }
30
- }
@@ -1,326 +0,0 @@
1
- import ora from 'ora';
2
- import path from 'path';
3
- import { Validator } from '../core/validation/validator.js';
4
- import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
5
- import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
6
- import { nearestMatches } from '../utils/match.js';
7
-
8
- type ItemType = 'change' | 'spec';
9
-
10
- interface ExecuteOptions {
11
- all?: boolean;
12
- changes?: boolean;
13
- specs?: boolean;
14
- type?: string;
15
- strict?: boolean;
16
- json?: boolean;
17
- noInteractive?: boolean;
18
- interactive?: boolean; // Commander sets this to false when --no-interactive is used
19
- concurrency?: string;
20
- }
21
-
22
- interface BulkItemResult {
23
- id: string;
24
- type: ItemType;
25
- valid: boolean;
26
- issues: { level: 'ERROR' | 'WARNING' | 'INFO'; path: string; message: string }[];
27
- durationMs: number;
28
- }
29
-
30
- export class ValidateCommand {
31
- async execute(itemName: string | undefined, options: ExecuteOptions = {}): Promise<void> {
32
- const interactive = isInteractive(options);
33
-
34
- // Handle bulk flags first
35
- if (options.all || options.changes || options.specs) {
36
- await this.runBulkValidation({
37
- changes: !!options.all || !!options.changes,
38
- specs: !!options.all || !!options.specs,
39
- }, { strict: !!options.strict, json: !!options.json, concurrency: options.concurrency, noInteractive: resolveNoInteractive(options) });
40
- return;
41
- }
42
-
43
- // No item and no flags
44
- if (!itemName) {
45
- if (interactive) {
46
- await this.runInteractiveSelector({ strict: !!options.strict, json: !!options.json, concurrency: options.concurrency });
47
- return;
48
- }
49
- this.printNonInteractiveHint();
50
- process.exitCode = 1;
51
- return;
52
- }
53
-
54
- // Direct item validation with type detection or override
55
- const typeOverride = this.normalizeType(options.type);
56
- await this.validateDirectItem(itemName, { typeOverride, strict: !!options.strict, json: !!options.json });
57
- }
58
-
59
- private normalizeType(value?: string): ItemType | undefined {
60
- if (!value) return undefined;
61
- const v = value.toLowerCase();
62
- if (v === 'change' || v === 'spec') return v;
63
- return undefined;
64
- }
65
-
66
- private async runInteractiveSelector(opts: { strict: boolean; json: boolean; concurrency?: string }): Promise<void> {
67
- const { select } = await import('@inquirer/prompts');
68
- const choice = await select({
69
- message: 'What would you like to validate?',
70
- choices: [
71
- { name: 'All (changes + specs)', value: 'all' },
72
- { name: 'All changes', value: 'changes' },
73
- { name: 'All specs', value: 'specs' },
74
- { name: 'Pick a specific change or spec', value: 'one' },
75
- ],
76
- });
77
-
78
- if (choice === 'all') return this.runBulkValidation({ changes: true, specs: true }, opts);
79
- if (choice === 'changes') return this.runBulkValidation({ changes: true, specs: false }, opts);
80
- if (choice === 'specs') return this.runBulkValidation({ changes: false, specs: true }, opts);
81
-
82
- // one
83
- const [changes, specs] = await Promise.all([getActiveChangeIds(), getSpecIds()]);
84
- const items: { name: string; value: { type: ItemType; id: string } }[] = [];
85
- items.push(...changes.map(id => ({ name: `change/${id}`, value: { type: 'change' as const, id } })));
86
- items.push(...specs.map(id => ({ name: `spec/${id}`, value: { type: 'spec' as const, id } })));
87
- if (items.length === 0) {
88
- console.error('No items found to validate.');
89
- process.exitCode = 1;
90
- return;
91
- }
92
- const picked = await select<{ type: ItemType; id: string }>({ message: 'Pick an item', choices: items });
93
- await this.validateByType(picked.type, picked.id, opts);
94
- }
95
-
96
- private printNonInteractiveHint(): void {
97
- console.error('Nothing to validate. Try one of:');
98
- console.error(' prompter validate --all');
99
- console.error(' prompter validate --changes');
100
- console.error(' prompter validate --specs');
101
- console.error(' prompter validate <item-name>');
102
- console.error('Or run in an interactive terminal.');
103
- }
104
-
105
- private async validateDirectItem(itemName: string, opts: { typeOverride?: ItemType; strict: boolean; json: boolean }): Promise<void> {
106
- const [changes, specs] = await Promise.all([getActiveChangeIds(), getSpecIds()]);
107
- const isChange = changes.includes(itemName);
108
- const isSpec = specs.includes(itemName);
109
-
110
- const type = opts.typeOverride ?? (isChange ? 'change' : isSpec ? 'spec' : undefined);
111
-
112
- if (!type) {
113
- console.error(`Unknown item '${itemName}'`);
114
- const suggestions = nearestMatches(itemName, [...changes, ...specs]);
115
- if (suggestions.length) console.error(`Did you mean: ${suggestions.join(', ')}?`);
116
- process.exitCode = 1;
117
- return;
118
- }
119
-
120
- if (!opts.typeOverride && isChange && isSpec) {
121
- console.error(`Ambiguous item '${itemName}' matches both a change and a spec.`);
122
- console.error('Pass --type change|spec, or use: prompter change validate / prompter spec validate');
123
- process.exitCode = 1;
124
- return;
125
- }
126
-
127
- await this.validateByType(type, itemName, opts);
128
- }
129
-
130
- private async validateByType(type: ItemType, id: string, opts: { strict: boolean; json: boolean }): Promise<void> {
131
- const validator = new Validator(opts.strict);
132
- if (type === 'change') {
133
- const changeDir = path.join(process.cwd(), 'prompter', 'changes', id);
134
- const start = Date.now();
135
- const report = await validator.validateChangeDeltaSpecs(changeDir);
136
- const durationMs = Date.now() - start;
137
- this.printReport('change', id, report, durationMs, opts.json);
138
- // Non-zero exit if invalid (keeps enriched output test semantics)
139
- process.exitCode = report.valid ? 0 : 1;
140
- return;
141
- }
142
- const file = path.join(process.cwd(), 'prompter', 'specs', id, 'spec.md');
143
- const start = Date.now();
144
- const report = await validator.validateSpec(file);
145
- const durationMs = Date.now() - start;
146
- this.printReport('spec', id, report, durationMs, opts.json);
147
- process.exitCode = report.valid ? 0 : 1;
148
- }
149
-
150
- private printReport(type: ItemType, id: string, report: { valid: boolean; issues: any[] }, durationMs: number, json: boolean): void {
151
- if (json) {
152
- const out = { items: [{ id, type, valid: report.valid, issues: report.issues, durationMs }], summary: { totals: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 }, byType: { [type]: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 } } }, version: '1.0' };
153
- console.log(JSON.stringify(out, null, 2));
154
- return;
155
- }
156
- if (report.valid) {
157
- console.log(`${type === 'change' ? 'Change' : 'Specification'} '${id}' is valid`);
158
- } else {
159
- console.error(`${type === 'change' ? 'Change' : 'Specification'} '${id}' has issues`);
160
- for (const issue of report.issues) {
161
- const label = issue.level === 'ERROR' ? 'ERROR' : issue.level;
162
- const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
163
- console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
164
- }
165
- this.printNextSteps(type);
166
- }
167
- }
168
-
169
- private printNextSteps(type: ItemType): void {
170
- const bullets: string[] = [];
171
- if (type === 'change') {
172
- bullets.push('- Ensure change has deltas in specs/: use headers ## ADDED/MODIFIED/REMOVED/RENAMED Requirements');
173
- bullets.push('- Each requirement MUST include at least one #### Scenario: block');
174
- bullets.push('- Debug parsed deltas: prompter change show <id> --json --deltas-only');
175
- } else {
176
- bullets.push('- Ensure spec includes ## Purpose and ## Requirements sections');
177
- bullets.push('- Each requirement MUST include at least one #### Scenario: block');
178
- bullets.push('- Re-run with --json to see structured report');
179
- }
180
- console.error('Next steps:');
181
- bullets.forEach(b => console.error(` ${b}`));
182
- }
183
-
184
- private async runBulkValidation(scope: { changes: boolean; specs: boolean }, opts: { strict: boolean; json: boolean; concurrency?: string; noInteractive?: boolean }): Promise<void> {
185
- const spinner = !opts.json && !opts.noInteractive ? ora('Validating...').start() : undefined;
186
- const [changeIds, specIds] = await Promise.all([
187
- scope.changes ? getActiveChangeIds() : Promise.resolve<string[]>([]),
188
- scope.specs ? getSpecIds() : Promise.resolve<string[]>([]),
189
- ]);
190
-
191
- const DEFAULT_CONCURRENCY = 6;
192
- const maxSuggestions = 5; // used by nearestMatches
193
- const concurrency = normalizeConcurrency(opts.concurrency) ?? normalizeConcurrency(process.env.PROMPTER_CONCURRENCY) ?? DEFAULT_CONCURRENCY;
194
- const validator = new Validator(opts.strict);
195
- const queue: Array<() => Promise<BulkItemResult>> = [];
196
-
197
- for (const id of changeIds) {
198
- queue.push(async () => {
199
- const start = Date.now();
200
- const changeDir = path.join(process.cwd(), 'prompter', 'changes', id);
201
- const report = await validator.validateChangeDeltaSpecs(changeDir);
202
- const durationMs = Date.now() - start;
203
- return { id, type: 'change' as const, valid: report.valid, issues: report.issues, durationMs };
204
- });
205
- }
206
- for (const id of specIds) {
207
- queue.push(async () => {
208
- const start = Date.now();
209
- const file = path.join(process.cwd(), 'prompter', 'specs', id, 'spec.md');
210
- const report = await validator.validateSpec(file);
211
- const durationMs = Date.now() - start;
212
- return { id, type: 'spec' as const, valid: report.valid, issues: report.issues, durationMs };
213
- });
214
- }
215
-
216
- if (queue.length === 0) {
217
- spinner?.stop();
218
-
219
- const summary = {
220
- totals: { items: 0, passed: 0, failed: 0 },
221
- byType: {
222
- ...(scope.changes ? { change: { items: 0, passed: 0, failed: 0 } } : {}),
223
- ...(scope.specs ? { spec: { items: 0, passed: 0, failed: 0 } } : {}),
224
- },
225
- } as const;
226
-
227
- if (opts.json) {
228
- const out = { items: [] as BulkItemResult[], summary, version: '1.0' };
229
- console.log(JSON.stringify(out, null, 2));
230
- } else {
231
- console.log('No items found to validate.');
232
- }
233
-
234
- process.exitCode = 0;
235
- return;
236
- }
237
-
238
- const results: BulkItemResult[] = [];
239
- let index = 0;
240
- let running = 0;
241
- let passed = 0;
242
- let failed = 0;
243
-
244
- await new Promise<void>((resolve) => {
245
- const next = () => {
246
- while (running < concurrency && index < queue.length) {
247
- const currentIndex = index++;
248
- const task = queue[currentIndex];
249
- running++;
250
- if (spinner) spinner.text = `Validating (${currentIndex + 1}/${queue.length})...`;
251
- task()
252
- .then(res => {
253
- results.push(res);
254
- if (res.valid) passed++; else failed++;
255
- })
256
- .catch((error: any) => {
257
- const message = error?.message || 'Unknown error';
258
- const res: BulkItemResult = { id: getPlannedId(currentIndex, changeIds, specIds) ?? 'unknown', type: getPlannedType(currentIndex, changeIds, specIds) ?? 'change', valid: false, issues: [{ level: 'ERROR', path: 'file', message }], durationMs: 0 };
259
- results.push(res);
260
- failed++;
261
- })
262
- .finally(() => {
263
- running--;
264
- if (index >= queue.length && running === 0) resolve();
265
- else next();
266
- });
267
- }
268
- };
269
- next();
270
- });
271
-
272
- spinner?.stop();
273
-
274
- results.sort((a, b) => a.id.localeCompare(b.id));
275
- const summary = {
276
- totals: { items: results.length, passed, failed },
277
- byType: {
278
- ...(scope.changes ? { change: summarizeType(results, 'change') } : {}),
279
- ...(scope.specs ? { spec: summarizeType(results, 'spec') } : {}),
280
- },
281
- } as const;
282
-
283
- if (opts.json) {
284
- const out = { items: results, summary, version: '1.0' };
285
- console.log(JSON.stringify(out, null, 2));
286
- } else {
287
- for (const res of results) {
288
- if (res.valid) console.log(`✓ ${res.type}/${res.id}`);
289
- else console.error(`✗ ${res.type}/${res.id}`);
290
- }
291
- console.log(`Totals: ${summary.totals.passed} passed, ${summary.totals.failed} failed (${summary.totals.items} items)`);
292
- }
293
-
294
- process.exitCode = failed > 0 ? 1 : 0;
295
- }
296
- }
297
-
298
- function summarizeType(results: BulkItemResult[], type: ItemType) {
299
- const filtered = results.filter(r => r.type === type);
300
- const items = filtered.length;
301
- const passed = filtered.filter(r => r.valid).length;
302
- const failed = items - passed;
303
- return { items, passed, failed };
304
- }
305
-
306
- function normalizeConcurrency(value?: string): number | undefined {
307
- if (!value) return undefined;
308
- const n = parseInt(value, 10);
309
- if (Number.isNaN(n) || n <= 0) return undefined;
310
- return n;
311
- }
312
-
313
- function getPlannedId(index: number, changeIds: string[], specIds: string[]): string | undefined {
314
- const totalChanges = changeIds.length;
315
- if (index < totalChanges) return changeIds[index];
316
- const specIndex = index - totalChanges;
317
- return specIds[specIndex];
318
- }
319
-
320
- function getPlannedType(index: number, changeIds: string[], specIds: string[]): ItemType | undefined {
321
- const totalChanges = changeIds.length;
322
- if (index < totalChanges) return 'change';
323
- const specIndex = index - totalChanges;
324
- if (specIndex >= 0 && specIndex < specIds.length) return 'spec';
325
- return undefined;
326
- }
@@ -1,167 +0,0 @@
1
- import type { Artifact, SchemaYaml, CompletedSet, BlockedArtifacts } from './types.js';
2
- import { loadSchema, parseSchema } from './schema.js';
3
-
4
- /**
5
- * Represents an artifact dependency graph.
6
- * Provides methods for querying build order, ready artifacts, and completion status.
7
- */
8
- export class ArtifactGraph {
9
- private artifacts: Map<string, Artifact>;
10
- private schema: SchemaYaml;
11
-
12
- private constructor(schema: SchemaYaml) {
13
- this.schema = schema;
14
- this.artifacts = new Map(schema.artifacts.map(a => [a.id, a]));
15
- }
16
-
17
- /**
18
- * Creates an ArtifactGraph from a YAML file path.
19
- */
20
- static fromYaml(filePath: string): ArtifactGraph {
21
- const schema = loadSchema(filePath);
22
- return new ArtifactGraph(schema);
23
- }
24
-
25
- /**
26
- * Creates an ArtifactGraph from YAML content string.
27
- */
28
- static fromYamlContent(yamlContent: string): ArtifactGraph {
29
- const schema = parseSchema(yamlContent);
30
- return new ArtifactGraph(schema);
31
- }
32
-
33
- /**
34
- * Creates an ArtifactGraph from a pre-validated schema object.
35
- */
36
- static fromSchema(schema: SchemaYaml): ArtifactGraph {
37
- return new ArtifactGraph(schema);
38
- }
39
-
40
- /**
41
- * Gets a single artifact by ID.
42
- */
43
- getArtifact(id: string): Artifact | undefined {
44
- return this.artifacts.get(id);
45
- }
46
-
47
- /**
48
- * Gets all artifacts in the graph.
49
- */
50
- getAllArtifacts(): Artifact[] {
51
- return Array.from(this.artifacts.values());
52
- }
53
-
54
- /**
55
- * Gets the schema name.
56
- */
57
- getName(): string {
58
- return this.schema.name;
59
- }
60
-
61
- /**
62
- * Gets the schema version.
63
- */
64
- getVersion(): number {
65
- return this.schema.version;
66
- }
67
-
68
- /**
69
- * Computes the topological build order using Kahn's algorithm.
70
- * Returns artifact IDs in the order they should be built.
71
- */
72
- getBuildOrder(): string[] {
73
- const inDegree = new Map<string, number>();
74
- const dependents = new Map<string, string[]>();
75
-
76
- // Initialize all artifacts
77
- for (const artifact of this.artifacts.values()) {
78
- inDegree.set(artifact.id, artifact.requires.length);
79
- dependents.set(artifact.id, []);
80
- }
81
-
82
- // Build reverse adjacency (who depends on whom)
83
- for (const artifact of this.artifacts.values()) {
84
- for (const req of artifact.requires) {
85
- dependents.get(req)!.push(artifact.id);
86
- }
87
- }
88
-
89
- // Start with roots (in-degree 0), sorted for determinism
90
- const queue = [...this.artifacts.keys()]
91
- .filter(id => inDegree.get(id) === 0)
92
- .sort();
93
-
94
- const result: string[] = [];
95
-
96
- while (queue.length > 0) {
97
- const current = queue.shift()!;
98
- result.push(current);
99
-
100
- // Collect newly ready artifacts, then sort before adding
101
- const newlyReady: string[] = [];
102
- for (const dep of dependents.get(current)!) {
103
- const newDegree = inDegree.get(dep)! - 1;
104
- inDegree.set(dep, newDegree);
105
- if (newDegree === 0) {
106
- newlyReady.push(dep);
107
- }
108
- }
109
- queue.push(...newlyReady.sort());
110
- }
111
-
112
- return result;
113
- }
114
-
115
- /**
116
- * Gets artifacts that are ready to be created (all dependencies completed).
117
- */
118
- getNextArtifacts(completed: CompletedSet): string[] {
119
- const ready: string[] = [];
120
-
121
- for (const artifact of this.artifacts.values()) {
122
- if (completed.has(artifact.id)) {
123
- continue; // Already completed
124
- }
125
-
126
- const allDepsCompleted = artifact.requires.every(req => completed.has(req));
127
- if (allDepsCompleted) {
128
- ready.push(artifact.id);
129
- }
130
- }
131
-
132
- // Sort for deterministic ordering
133
- return ready.sort();
134
- }
135
-
136
- /**
137
- * Checks if all artifacts in the graph are completed.
138
- */
139
- isComplete(completed: CompletedSet): boolean {
140
- for (const artifact of this.artifacts.values()) {
141
- if (!completed.has(artifact.id)) {
142
- return false;
143
- }
144
- }
145
- return true;
146
- }
147
-
148
- /**
149
- * Gets blocked artifacts and their unmet dependencies.
150
- */
151
- getBlocked(completed: CompletedSet): BlockedArtifacts {
152
- const blocked: BlockedArtifacts = {};
153
-
154
- for (const artifact of this.artifacts.values()) {
155
- if (completed.has(artifact.id)) {
156
- continue; // Already completed
157
- }
158
-
159
- const unmetDeps = artifact.requires.filter(req => !completed.has(req));
160
- if (unmetDeps.length > 0) {
161
- blocked[artifact.id] = unmetDeps.sort();
162
- }
163
- }
164
-
165
- return blocked;
166
- }
167
- }