wogiflow 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 (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,487 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Prompt Composer
5
+ *
6
+ * Assembles prompt fragments into complete prompts tailored
7
+ * to specific models and CLIs.
8
+ *
9
+ * Part of Phase 2: Multi-Model Core
10
+ *
11
+ * Usage:
12
+ * flow prompt-compose --model claude-sonnet-4 --task-type feature
13
+ * flow prompt-compose --model gemini-2-flash --domain api
14
+ * flow prompt-compose --list-fragments
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const {
20
+ PROJECT_ROOT,
21
+ parseFlags,
22
+ outputJson,
23
+ color,
24
+ info,
25
+ warn,
26
+ error,
27
+ fileExists,
28
+ dirExists,
29
+ printHeader,
30
+ printSection,
31
+ isPathWithinProject
32
+ } = require('./flow-utils');
33
+
34
+ // ============================================================
35
+ // Constants
36
+ // ============================================================
37
+
38
+ const FRAGMENTS_DIR = path.join(PROJECT_ROOT, '.workflow', 'prompts', 'fragments');
39
+ const COMPOSED_DIR = path.join(PROJECT_ROOT, '.workflow', 'prompts', 'composed');
40
+
41
+ // Model to CLI mapping
42
+ const MODEL_CLI_MAP = {
43
+ 'claude-opus-4-5': 'claude-code',
44
+ 'claude-sonnet-4': 'claude-code',
45
+ 'claude-haiku-3-5': 'claude-code',
46
+ 'gpt-4o': null,
47
+ 'gemini-2-flash': 'gemini-cli'
48
+ };
49
+
50
+ // ============================================================
51
+ // Fragment Loading
52
+ // ============================================================
53
+
54
+ /**
55
+ * Parse fragment front matter
56
+ * @param {string} content - Fragment file content
57
+ * @returns {Object} Parsed fragment with metadata and content
58
+ */
59
+ function parseFragment(content) {
60
+ const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
61
+
62
+ if (!frontMatterMatch) {
63
+ return {
64
+ metadata: {},
65
+ content: content.trim(),
66
+ _parseErrors: []
67
+ };
68
+ }
69
+
70
+ const frontMatter = frontMatterMatch[1];
71
+ const body = frontMatterMatch[2].trim();
72
+
73
+ // Parse YAML-like front matter with error tracking
74
+ const metadata = {};
75
+ const parseErrors = [];
76
+
77
+ try {
78
+ for (const line of frontMatter.split('\n')) {
79
+ const colonIndex = line.indexOf(':');
80
+ if (colonIndex === -1) {
81
+ // Skip empty lines silently, warn about malformed lines
82
+ if (line.trim() && !line.trim().startsWith('#')) {
83
+ parseErrors.push(`Malformed line (no colon): "${line.slice(0, 50)}"`);
84
+ }
85
+ continue;
86
+ }
87
+
88
+ const key = line.slice(0, colonIndex).trim();
89
+ if (!key) {
90
+ parseErrors.push(`Empty key in line: "${line.slice(0, 50)}"`);
91
+ continue;
92
+ }
93
+
94
+ let value = line.slice(colonIndex + 1).trim();
95
+
96
+ // Parse arrays
97
+ if (value.startsWith('[') && value.endsWith(']')) {
98
+ try {
99
+ value = value.slice(1, -1).split(',').map(v => v.trim());
100
+ } catch {
101
+ parseErrors.push(`Failed to parse array for key "${key}"`);
102
+ continue;
103
+ }
104
+ }
105
+ // Parse numbers
106
+ else if (/^\d+$/.test(value)) {
107
+ value = parseInt(value, 10);
108
+ }
109
+
110
+ metadata[key] = value;
111
+ }
112
+ } catch (err) {
113
+ parseErrors.push(`Unexpected error: ${err.message}`);
114
+ }
115
+
116
+ // Log warnings for parse errors
117
+ if (parseErrors.length > 0) {
118
+ warn(`Fragment parse warnings: ${parseErrors.join('; ')}`);
119
+ }
120
+
121
+ return { metadata, content: body, _parseErrors: parseErrors };
122
+ }
123
+
124
+ /**
125
+ * Load all fragments from directory
126
+ * @returns {Object[]} Array of loaded fragments
127
+ */
128
+ function loadFragments() {
129
+ if (!dirExists(FRAGMENTS_DIR)) {
130
+ return [];
131
+ }
132
+
133
+ const fragments = [];
134
+
135
+ let files;
136
+ try {
137
+ files = fs.readdirSync(FRAGMENTS_DIR).filter(f => f.endsWith('.md'));
138
+ } catch (err) {
139
+ warn(`Could not read fragments directory: ${err.message}`);
140
+ return [];
141
+ }
142
+
143
+ for (const file of files) {
144
+ const filePath = path.join(FRAGMENTS_DIR, file);
145
+
146
+ try {
147
+ const content = fs.readFileSync(filePath, 'utf-8');
148
+ const parsed = parseFragment(content);
149
+
150
+ fragments.push({
151
+ file,
152
+ path: filePath,
153
+ ...parsed
154
+ });
155
+ } catch (err) {
156
+ warn(`Could not read fragment ${file}: ${err.message}`);
157
+ // Continue with other fragments
158
+ }
159
+ }
160
+
161
+ return fragments;
162
+ }
163
+
164
+ /**
165
+ * Filter fragments for a specific model and context
166
+ * @param {Object[]} fragments - All fragments
167
+ * @param {Object} filter - Filter criteria
168
+ * @returns {Object[]} Filtered and sorted fragments
169
+ */
170
+ function filterFragments(fragments, filter) {
171
+ const { model, cli, domain, purpose } = filter;
172
+
173
+ return fragments.filter(f => {
174
+ const meta = f.metadata;
175
+
176
+ // Check model compatibility
177
+ if (meta.models && meta.models !== 'all') {
178
+ const models = Array.isArray(meta.models) ? meta.models : [meta.models];
179
+ if (!models.includes(model) && !models.includes('all')) {
180
+ return false;
181
+ }
182
+ }
183
+
184
+ // Check CLI compatibility
185
+ if (meta.cli && meta.cli !== 'all') {
186
+ const clis = Array.isArray(meta.cli) ? meta.cli : [meta.cli];
187
+ if (!clis.includes(cli) && !clis.includes('all')) {
188
+ return false;
189
+ }
190
+ }
191
+
192
+ // Check domain if specified
193
+ if (domain && meta.domain && meta.domain !== domain) {
194
+ return false;
195
+ }
196
+
197
+ // Check purpose if specified
198
+ if (purpose && meta.purpose && meta.purpose !== purpose) {
199
+ return false;
200
+ }
201
+
202
+ return true;
203
+ }).sort((a, b) => {
204
+ // Sort by order (lower first)
205
+ const orderA = a.metadata.order || 50;
206
+ const orderB = b.metadata.order || 50;
207
+ return orderA - orderB;
208
+ });
209
+ }
210
+
211
+ // ============================================================
212
+ // Prompt Composition
213
+ // ============================================================
214
+
215
+ /**
216
+ * Compose prompt from fragments
217
+ * @param {Object} params - Composition parameters
218
+ * @returns {Object} Composed prompt
219
+ */
220
+ function composePrompt(params) {
221
+ const {
222
+ model,
223
+ taskType = 'feature',
224
+ domain = null,
225
+ taskData = null,
226
+ includeCore = true
227
+ } = params;
228
+
229
+ // Get CLI for model
230
+ const cli = MODEL_CLI_MAP[model] || 'claude-code';
231
+
232
+ // Load and filter fragments
233
+ const allFragments = loadFragments();
234
+ const filtered = filterFragments(allFragments, {
235
+ model,
236
+ cli,
237
+ domain
238
+ });
239
+
240
+ // Separate by purpose
241
+ const coreFragments = filtered.filter(f => f.metadata.purpose === 'core');
242
+ const qualityFragments = filtered.filter(f => f.metadata.purpose === 'quality');
243
+ const domainFragments = filtered.filter(f => f.metadata.purpose === 'domain');
244
+ const formatFragments = filtered.filter(f => f.metadata.purpose === 'formatting');
245
+
246
+ // Build sections
247
+ const sections = [];
248
+
249
+ // Core context
250
+ if (includeCore && coreFragments.length > 0) {
251
+ sections.push({
252
+ name: 'Task Context',
253
+ fragments: coreFragments
254
+ });
255
+ }
256
+
257
+ // Quality guidelines
258
+ if (qualityFragments.length > 0) {
259
+ sections.push({
260
+ name: 'Quality Guidelines',
261
+ fragments: qualityFragments
262
+ });
263
+ }
264
+
265
+ // Domain-specific
266
+ if (domainFragments.length > 0) {
267
+ sections.push({
268
+ name: 'Domain Guidelines',
269
+ fragments: domainFragments
270
+ });
271
+ }
272
+
273
+ // Output format (model-specific)
274
+ if (formatFragments.length > 0) {
275
+ sections.push({
276
+ name: 'Output Format',
277
+ fragments: formatFragments
278
+ });
279
+ }
280
+
281
+ // Compose full prompt
282
+ let fullPrompt = '';
283
+
284
+ for (const section of sections) {
285
+ for (const fragment of section.fragments) {
286
+ fullPrompt += fragment.content + '\n\n';
287
+ }
288
+ }
289
+
290
+ // Apply template substitution if task data provided
291
+ if (taskData) {
292
+ fullPrompt = applyTemplate(fullPrompt, taskData);
293
+ }
294
+
295
+ return {
296
+ model,
297
+ cli,
298
+ domain,
299
+ taskType,
300
+ sections: sections.map(s => ({
301
+ name: s.name,
302
+ fragments: s.fragments.map(f => f.metadata.id || f.file)
303
+ })),
304
+ fragmentCount: filtered.length,
305
+ prompt: fullPrompt.trim(),
306
+ tokenEstimate: Math.ceil(fullPrompt.length / 4) // Rough estimate
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Apply handlebars-like template substitution
312
+ * @param {string} template - Template string
313
+ * @param {Object} data - Data to substitute
314
+ * @returns {string} Processed string
315
+ */
316
+ function applyTemplate(template, data) {
317
+ // Forbidden keys to prevent prototype pollution (case-insensitive)
318
+ const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
319
+
320
+ // Simple substitution: {{key}} or {{object.key}}
321
+ return template.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
322
+ const keys = path.trim().split('.');
323
+ let value = data;
324
+
325
+ for (const key of keys) {
326
+ // Prevent prototype pollution attacks (case-insensitive check)
327
+ const keyLower = key.toLowerCase();
328
+ if (FORBIDDEN_KEYS.has(keyLower)) return match;
329
+ if (value === undefined || value === null) return match;
330
+ // Only access own properties
331
+ if (!Object.prototype.hasOwnProperty.call(value, key)) return match;
332
+ value = value[key];
333
+ }
334
+
335
+ if (Array.isArray(value)) {
336
+ return value.join('\n');
337
+ }
338
+
339
+ return value !== undefined ? String(value) : match;
340
+ });
341
+ }
342
+
343
+ // ============================================================
344
+ // CLI Output
345
+ // ============================================================
346
+
347
+ /**
348
+ * List all available fragments
349
+ */
350
+ function listFragments() {
351
+ const fragments = loadFragments();
352
+
353
+ printHeader('PROMPT FRAGMENTS');
354
+
355
+ if (fragments.length === 0) {
356
+ info('No fragments found in ' + FRAGMENTS_DIR);
357
+ return;
358
+ }
359
+
360
+ // Group by purpose
361
+ const byPurpose = {};
362
+ for (const f of fragments) {
363
+ const purpose = f.metadata.purpose || 'other';
364
+ if (!byPurpose[purpose]) byPurpose[purpose] = [];
365
+ byPurpose[purpose].push(f);
366
+ }
367
+
368
+ for (const [purpose, frags] of Object.entries(byPurpose)) {
369
+ printSection(purpose.charAt(0).toUpperCase() + purpose.slice(1));
370
+
371
+ for (const f of frags) {
372
+ const models = f.metadata.models === 'all' ? 'all' :
373
+ (Array.isArray(f.metadata.models) ? f.metadata.models.join(', ') : f.metadata.models);
374
+ console.log(` ${color('cyan', f.metadata.id || f.file)}`);
375
+ console.log(` Models: ${models}`);
376
+ console.log(` Order: ${f.metadata.order || 50}`);
377
+ if (f.metadata.description) {
378
+ console.log(` ${f.metadata.description}`);
379
+ }
380
+ console.log('');
381
+ }
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Print composed prompt summary
387
+ * @param {Object} composed - Composed prompt result
388
+ */
389
+ function printComposed(composed) {
390
+ printHeader('COMPOSED PROMPT');
391
+
392
+ printSection('Configuration');
393
+ console.log(` Model: ${color('cyan', composed.model)}`);
394
+ console.log(` CLI: ${composed.cli}`);
395
+ if (composed.domain) {
396
+ console.log(` Domain: ${composed.domain}`);
397
+ }
398
+ console.log(` Task Type: ${composed.taskType}`);
399
+
400
+ printSection('Sections');
401
+ for (const section of composed.sections) {
402
+ console.log(` ${section.name}:`);
403
+ for (const frag of section.fragments) {
404
+ console.log(` - ${frag}`);
405
+ }
406
+ }
407
+
408
+ printSection('Stats');
409
+ console.log(` Fragments: ${composed.fragmentCount}`);
410
+ console.log(` Estimated tokens: ~${composed.tokenEstimate.toLocaleString()}`);
411
+
412
+ if (composed.prompt) {
413
+ printSection('Preview (first 500 chars)');
414
+ console.log(` ${composed.prompt.slice(0, 500)}...`);
415
+ }
416
+
417
+ console.log('');
418
+ }
419
+
420
+ // ============================================================
421
+ // Main
422
+ // ============================================================
423
+
424
+ async function main() {
425
+ const { positional, flags } = parseFlags(process.argv.slice(2));
426
+
427
+ // List fragments mode
428
+ if (flags['list-fragments'] || positional[0] === 'list') {
429
+ listFragments();
430
+ return;
431
+ }
432
+
433
+ // Compose prompt
434
+ const model = flags.model || 'claude-sonnet-4';
435
+ const taskType = flags['task-type'] || flags.type || 'feature';
436
+ const domain = flags.domain || null;
437
+
438
+ const composed = composePrompt({
439
+ model,
440
+ taskType,
441
+ domain,
442
+ includeCore: true
443
+ });
444
+
445
+ // Output
446
+ if (flags.json) {
447
+ outputJson({
448
+ success: true,
449
+ ...composed
450
+ });
451
+ } else {
452
+ printComposed(composed);
453
+ }
454
+
455
+ // Optionally save to file
456
+ if (flags.output) {
457
+ const outputPath = path.isAbsolute(flags.output)
458
+ ? flags.output
459
+ : path.join(PROJECT_ROOT, flags.output);
460
+
461
+ // Validate path is within project to prevent path traversal
462
+ if (!isPathWithinProject(outputPath)) {
463
+ error('Output path must be within project directory');
464
+ process.exit(1);
465
+ }
466
+
467
+ fs.writeFileSync(outputPath, composed.prompt);
468
+ info(`Saved to: ${outputPath}`);
469
+ }
470
+ }
471
+
472
+ // Export for use by other scripts
473
+ module.exports = {
474
+ composePrompt,
475
+ loadFragments,
476
+ filterFragments,
477
+ parseFragment,
478
+ applyTemplate,
479
+ MODEL_CLI_MAP
480
+ };
481
+
482
+ if (require.main === module) {
483
+ main().catch(err => {
484
+ error(err.message);
485
+ process.exit(1);
486
+ });
487
+ }