@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.
- package/CHANGELOG.md +35 -0
- package/README.md +105 -77
- package/dist/cli/index.js +25 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +35 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +56 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +4 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +14 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/update.d.ts +0 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +19 -48
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/whoami.d.ts +4 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +42 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/core/auth-store.d.ts +10 -0
- package/dist/core/auth-store.d.ts.map +1 -0
- package/dist/core/auth-store.js +39 -0
- package/dist/core/auth-store.js.map +1 -0
- package/dist/core/config.d.ts +0 -7
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +0 -128
- package/dist/core/config.js.map +1 -1
- package/dist/core/registry.d.ts +18 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +94 -0
- package/dist/core/registry.js.map +1 -0
- package/package.json +7 -1
- package/AGENTS.md +0 -123
- package/CLAUDE.md +0 -17
- package/build.js +0 -20
- package/convex-setup.md +0 -403
- package/dist/core/prompt-templates.d.ts +0 -23
- package/dist/core/prompt-templates.d.ts.map +0 -1
- package/dist/core/prompt-templates.js +0 -3485
- package/dist/core/prompt-templates.js.map +0 -1
- package/prompt/ai-humanizer.md +0 -45
- package/prompt/api-contract-generator.md +0 -234
- package/prompt/apply.md +0 -17
- package/prompt/archive.md +0 -21
- package/prompt/design-system.md +0 -210
- package/prompt/document-explainer.md +0 -149
- package/prompt/epic-generator.md +0 -198
- package/prompt/epic-single.md +0 -47
- package/prompt/erd-generator.md +0 -130
- package/prompt/fsd-generator.md +0 -157
- package/prompt/prd-agent-generator.md +0 -147
- package/prompt/prd-generator.md +0 -195
- package/prompt/product-brief.md +0 -289
- package/prompt/proposal.md +0 -22
- package/prompt/qa-test-scenario.md +0 -133
- package/prompt/skill-creator.md +0 -350
- package/prompt/story-generator.md +0 -278
- package/prompt/story-single.md +0 -70
- package/prompt/tdd-generator.md +0 -294
- package/prompt/tdd-lite-generator.md +0 -224
- package/prompt/wireframe-generator.md +0 -219
- package/skills/ai-context-generator/SKILL.md +0 -54
- package/skills/ai-context-generator/references/AGENTS.template.md +0 -83
- package/skills/ai-context-generator/references/CLAUDE.template.md +0 -39
- package/skills/ai-context-generator/references/behavioral-guidelines.md +0 -71
- package/skills/ai-context-generator/references/discovery-checklist.md +0 -40
- package/skills/ai-context-generator/references/examples/AGENTS.good.md +0 -103
- package/skills/ai-context-generator/references/extraction-checklist.md +0 -23
- package/skills/ai-context-generator/references/overlays/laravel.md +0 -44
- package/skills/ai-humanizer/SKILL.md +0 -50
- package/skills/api-contract-generator/SKILL.md +0 -243
- package/skills/apply/SKILL.md +0 -23
- package/skills/archive/SKILL.md +0 -27
- package/skills/cerebro/SKILL.md +0 -187
- package/skills/cerebro/references/agents.md +0 -213
- package/skills/code-review/SKILL.md +0 -373
- package/skills/code-review/assets/report-template-agent.md +0 -212
- package/skills/code-review/assets/report-template-compact.md +0 -81
- package/skills/code-review/assets/report-template-full.md +0 -264
- package/skills/code-review/assets/report-template-human.md +0 -168
- package/skills/code-review/references/universal-patterns.md +0 -495
- package/skills/design-md/README.md +0 -34
- package/skills/design-md/SKILL.md +0 -172
- package/skills/design-md/examples/DESIGN.md +0 -154
- package/skills/design-system/SKILL.md +0 -216
- package/skills/design-system-generator/SKILL.md +0 -324
- package/skills/design-system-generator/assets/design-system-template.md +0 -348
- package/skills/design-system-generator/references/extraction-patterns.md +0 -321
- package/skills/doc-builder/SKILL.md +0 -115
- package/skills/doc-builder/references/ui-patterns.md +0 -394
- package/skills/document-explainer/SKILL.md +0 -155
- package/skills/document-translator/SKILL.md +0 -58
- package/skills/enhance/SKILL.md +0 -47
- package/skills/enhance-prompt/README.md +0 -34
- package/skills/enhance-prompt/SKILL.md +0 -204
- package/skills/enhance-prompt/references/KEYWORDS.md +0 -114
- package/skills/epic-generator/SKILL.md +0 -204
- package/skills/epic-single/SKILL.md +0 -63
- package/skills/erd-generator/SKILL.md +0 -138
- package/skills/feature-planner/SKILL.md +0 -305
- package/skills/feature-planner/assets/implementation-plan-template.md +0 -85
- package/skills/frontend-design/LICENSE.txt +0 -177
- package/skills/frontend-design/SKILL.md +0 -42
- package/skills/fsd-generator/SKILL.md +0 -163
- package/skills/gamma-builder/SKILL.md +0 -134
- package/skills/laravel-code-review/SKILL.md +0 -383
- package/skills/laravel-code-review/assets/report-template-agent.md +0 -195
- package/skills/laravel-code-review/assets/report-template-compact.md +0 -79
- package/skills/laravel-code-review/assets/report-template-full.md +0 -253
- package/skills/laravel-code-review/assets/report-template-human.md +0 -159
- package/skills/laravel-code-review/references/laravel-patterns.md +0 -571
- package/skills/laravel-code-review/references/php84-features.md +0 -442
- package/skills/mcp-builder/LICENSE.txt +0 -202
- package/skills/mcp-builder/SKILL.md +0 -236
- package/skills/mcp-builder/reference/evaluation.md +0 -602
- package/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
- package/skills/mcp-builder/reference/node_mcp_server.md +0 -970
- package/skills/mcp-builder/reference/python_mcp_server.md +0 -719
- package/skills/mcp-builder/scripts/connections.py +0 -151
- package/skills/mcp-builder/scripts/evaluation.py +0 -373
- package/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
- package/skills/mcp-builder/scripts/requirements.txt +0 -2
- package/skills/meeting-notes/SKILL.md +0 -159
- package/skills/meeting-notes/evals/evals.json +0 -23
- package/skills/prd-agent-generator/SKILL.md +0 -132
- package/skills/prd-generator/SKILL.md +0 -211
- package/skills/product-brief/SKILL.md +0 -141
- package/skills/project-orchestrator/SKILL.md +0 -487
- package/skills/project-orchestrator/assets/caddy-vps-setup.md +0 -180
- package/skills/project-orchestrator/assets/plan-summary-template.md +0 -159
- package/skills/prompter-specs/SKILL.md +0 -115
- package/skills/prompter-workflow/SKILL.md +0 -166
- package/skills/prompter-workflow/evals/evals.json +0 -89
- package/skills/proposal/SKILL.md +0 -28
- package/skills/qa-test-scenario/SKILL.md +0 -149
- package/skills/skill-creator/SKILL.md +0 -173
- package/skills/sph-generator/SKILL.md +0 -488
- package/skills/story-generator/SKILL.md +0 -285
- package/skills/story-single/SKILL.md +0 -86
- package/skills/tdd-generator/SKILL.md +0 -300
- package/skills/tdd-lite-generator/SKILL.md +0 -230
- package/skills/ui-ux-pro/SKILL.md +0 -199
- package/skills/ui-ux-pro/assets/design-spec-template.md +0 -173
- package/skills/ui-ux-pro/references/component-patterns.md +0 -255
- package/skills/ui-ux-pro/references/design-principles.md +0 -167
- package/skills/wireframe-generator/SKILL.md +0 -227
- package/src/cli/index.ts +0 -223
- package/src/commands/archive.ts +0 -302
- package/src/commands/change.ts +0 -292
- package/src/commands/config.ts +0 -233
- package/src/commands/guide.ts +0 -50
- package/src/commands/init.ts +0 -597
- package/src/commands/list.ts +0 -194
- package/src/commands/show.ts +0 -138
- package/src/commands/spec.ts +0 -251
- package/src/commands/update.ts +0 -129
- package/src/commands/upgrade.ts +0 -30
- package/src/commands/validate.ts +0 -326
- package/src/core/artifact-graph/graph.ts +0 -167
- package/src/core/artifact-graph/index.ts +0 -44
- package/src/core/artifact-graph/instruction-loader.ts +0 -302
- package/src/core/artifact-graph/resolver.ts +0 -226
- package/src/core/artifact-graph/schema.ts +0 -124
- package/src/core/artifact-graph/state.ts +0 -64
- package/src/core/artifact-graph/types.ts +0 -65
- package/src/core/completions/command-registry.ts +0 -382
- package/src/core/completions/completion-provider.ts +0 -128
- package/src/core/completions/generators/bash-generator.ts +0 -191
- package/src/core/completions/generators/fish-generator.ts +0 -188
- package/src/core/completions/generators/powershell-generator.ts +0 -223
- package/src/core/completions/generators/zsh-generator.ts +0 -281
- package/src/core/completions/templates/bash-templates.ts +0 -24
- package/src/core/completions/templates/fish-templates.ts +0 -40
- package/src/core/completions/templates/powershell-templates.ts +0 -25
- package/src/core/completions/templates/zsh-templates.ts +0 -36
- package/src/core/completions/types.ts +0 -90
- package/src/core/config-schema.ts +0 -230
- package/src/core/config.ts +0 -181
- package/src/core/configurators/slash/antigravity.ts +0 -10
- package/src/core/configurators/slash/base.ts +0 -109
- package/src/core/configurators/slash/claude.ts +0 -10
- package/src/core/configurators/slash/codex.ts +0 -10
- package/src/core/configurators/slash/droid.ts +0 -10
- package/src/core/configurators/slash/forge.ts +0 -10
- package/src/core/configurators/slash/github-copilot.ts +0 -10
- package/src/core/configurators/slash/index.ts +0 -10
- package/src/core/configurators/slash/kilocode.ts +0 -10
- package/src/core/configurators/slash/opencode.ts +0 -10
- package/src/core/configurators/slash/registry.ts +0 -51
- package/src/core/converters/json-converter.ts +0 -62
- package/src/core/global-config.ts +0 -136
- package/src/core/parsers/change-parser.ts +0 -234
- package/src/core/parsers/markdown-parser.ts +0 -237
- package/src/core/parsers/requirement-blocks.ts +0 -234
- package/src/core/prompt-templates.ts +0 -3504
- package/src/core/schemas/base.schema.ts +0 -20
- package/src/core/schemas/change.schema.ts +0 -42
- package/src/core/schemas/index.ts +0 -20
- package/src/core/schemas/spec.schema.ts +0 -17
- package/src/core/skill-discovery.ts +0 -68
- package/src/core/specs-apply.ts +0 -483
- package/src/core/styles/palette.ts +0 -8
- package/src/core/templates/agents-template.ts +0 -459
- package/src/core/templates/claude-template.ts +0 -2
- package/src/core/templates/index.ts +0 -3
- package/src/core/templates/project-template.ts +0 -32
- package/src/core/validation/constants.ts +0 -48
- package/src/core/validation/types.ts +0 -19
- package/src/core/validation/validator.ts +0 -449
- package/src/core/view.ts +0 -219
- package/src/index.ts +0 -1
- package/src/utils/change-metadata.ts +0 -171
- package/src/utils/change-utils.ts +0 -131
- package/src/utils/file-system.ts +0 -252
- package/src/utils/index.ts +0 -12
- package/src/utils/interactive.ts +0 -29
- package/src/utils/item-discovery.ts +0 -66
- package/src/utils/match.ts +0 -26
- package/src/utils/shell-detection.ts +0 -62
- package/src/utils/task-progress.ts +0 -43
- package/tsconfig.json +0 -28
package/src/commands/list.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/show.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/spec.ts
DELETED
|
@@ -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
|
-
}
|