agileflow 2.94.1 → 2.95.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 (73) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/colors.generated.js +117 -0
  3. package/lib/colors.js +59 -109
  4. package/lib/generator-factory.js +333 -0
  5. package/lib/path-utils.js +49 -0
  6. package/lib/session-registry.js +25 -15
  7. package/lib/smart-json-file.js +40 -32
  8. package/lib/state-machine.js +286 -0
  9. package/package.json +1 -1
  10. package/scripts/agileflow-configure.js +7 -6
  11. package/scripts/archive-completed-stories.sh +86 -11
  12. package/scripts/babysit-context-restore.js +89 -0
  13. package/scripts/claude-tmux.sh +111 -5
  14. package/scripts/damage-control/bash-tool-damage-control.js +11 -247
  15. package/scripts/damage-control/edit-tool-damage-control.js +9 -249
  16. package/scripts/damage-control/write-tool-damage-control.js +9 -244
  17. package/scripts/generate-colors.js +314 -0
  18. package/scripts/lib/colors.generated.sh +82 -0
  19. package/scripts/lib/colors.sh +10 -70
  20. package/scripts/lib/configure-features.js +401 -0
  21. package/scripts/lib/context-loader.js +181 -52
  22. package/scripts/precompact-context.sh +54 -17
  23. package/scripts/session-coordinator.sh +2 -2
  24. package/scripts/session-manager.js +653 -10
  25. package/src/core/commands/audit.md +93 -0
  26. package/src/core/commands/auto.md +73 -0
  27. package/src/core/commands/babysit.md +169 -13
  28. package/src/core/commands/baseline.md +73 -0
  29. package/src/core/commands/batch.md +64 -0
  30. package/src/core/commands/blockers.md +60 -0
  31. package/src/core/commands/board.md +66 -0
  32. package/src/core/commands/choose.md +77 -0
  33. package/src/core/commands/ci.md +77 -0
  34. package/src/core/commands/compress.md +27 -1
  35. package/src/core/commands/configure.md +126 -10
  36. package/src/core/commands/council.md +74 -0
  37. package/src/core/commands/debt.md +72 -0
  38. package/src/core/commands/deploy.md +73 -0
  39. package/src/core/commands/deps.md +68 -0
  40. package/src/core/commands/docs.md +60 -0
  41. package/src/core/commands/feedback.md +68 -0
  42. package/src/core/commands/ideate.md +74 -0
  43. package/src/core/commands/impact.md +74 -0
  44. package/src/core/commands/install.md +529 -0
  45. package/src/core/commands/maintain.md +558 -0
  46. package/src/core/commands/metrics.md +75 -0
  47. package/src/core/commands/multi-expert.md +74 -0
  48. package/src/core/commands/packages.md +69 -0
  49. package/src/core/commands/readme-sync.md +64 -0
  50. package/src/core/commands/research/analyze.md +285 -121
  51. package/src/core/commands/research/import.md +281 -109
  52. package/src/core/commands/retro.md +76 -0
  53. package/src/core/commands/review.md +72 -0
  54. package/src/core/commands/rlm.md +83 -0
  55. package/src/core/commands/rpi.md +90 -0
  56. package/src/core/commands/session/cleanup.md +214 -12
  57. package/src/core/commands/session/end.md +155 -17
  58. package/src/core/commands/sprint.md +72 -0
  59. package/src/core/commands/story-validate.md +68 -0
  60. package/src/core/commands/template.md +69 -0
  61. package/src/core/commands/tests.md +83 -0
  62. package/src/core/commands/update.md +59 -0
  63. package/src/core/commands/validate-expertise.md +76 -0
  64. package/src/core/commands/velocity.md +74 -0
  65. package/src/core/commands/verify.md +91 -0
  66. package/src/core/commands/whats-new.md +69 -0
  67. package/src/core/commands/workflow.md +88 -0
  68. package/src/core/templates/command-documentation.md +187 -0
  69. package/tools/cli/commands/session.js +1171 -0
  70. package/tools/cli/commands/setup.js +2 -81
  71. package/tools/cli/installers/core/installer.js +0 -5
  72. package/tools/cli/installers/ide/claude-code.js +6 -0
  73. package/tools/cli/lib/config-manager.js +42 -5
package/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.95.0] - 2026-01-30
11
+
12
+ ### Added
13
+ - Global session management commands (status, history, cleanup)
14
+ - `/agileflow:install` and `/agileflow:maintain` commands for quick setup
15
+ - Multi-expert Implementation Ideation in `/research:import` and `/research:analyze`
16
+ - Plan file context preservation with experimental profile
17
+ - Tmux freeze recovery keybinds and `--rescue` flag
18
+ - Shellcheck CI job for shell script linting
19
+
20
+ ### Fixed
21
+ - Prevent worktree sessions from being marked as main
22
+ - Prevent duplicate agents during install/update
23
+ - Inline uncommitted changes handling for `/session:end`
24
+
10
25
  ## [2.94.1] - 2026-01-24
11
26
 
12
27
  ### Added
@@ -0,0 +1,117 @@
1
+ /**
2
+ * colors.generated.js - Auto-generated from config/colors.yaml
3
+ *
4
+ * DO NOT EDIT THIS FILE DIRECTLY.
5
+ * Run: node scripts/generate-colors.js
6
+ *
7
+ * Generated: 2026-01-29T08:28:33.726Z
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ // Brand color
13
+ const BRAND_HEX = '#e8683a';
14
+
15
+ // Modifiers
16
+ const modifiers = {
17
+ reset: '\x1b[0m',
18
+ bold: '\x1b[1m',
19
+ dim: '\x1b[2m',
20
+ italic: '\x1b[3m',
21
+ underline: '\x1b[4m',
22
+ };
23
+
24
+ // Standard ANSI colors
25
+ const standard = {
26
+ red: '\x1b[31m',
27
+ green: '\x1b[32m',
28
+ yellow: '\x1b[33m',
29
+ blue: '\x1b[34m',
30
+ magenta: '\x1b[35m',
31
+ cyan: '\x1b[36m',
32
+ white: '\x1b[37m',
33
+ black: '\x1b[30m',
34
+ };
35
+
36
+ // Bright ANSI colors
37
+ const bright = {
38
+ brightRed: '\x1b[91m',
39
+ brightGreen: '\x1b[92m',
40
+ brightYellow: '\x1b[93m',
41
+ brightBlue: '\x1b[94m',
42
+ brightMagenta: '\x1b[95m',
43
+ brightCyan: '\x1b[96m',
44
+ brightWhite: '\x1b[97m',
45
+ brightBlack: '\x1b[90m',
46
+ };
47
+
48
+ // 256-color palette
49
+ const palette256 = {
50
+ mintGreen: '\x1b[38;5;158m', // Healthy/success states
51
+ peach: '\x1b[38;5;215m', // Warning states
52
+ coral: '\x1b[38;5;203m', // Critical/error states
53
+ lightGreen: '\x1b[38;5;194m', // Session healthy
54
+ lightYellow: '\x1b[38;5;228m', // Session warning
55
+ lightPink: '\x1b[38;5;210m', // Session critical
56
+ skyBlue: '\x1b[38;5;117m', // Directories/paths, ready states
57
+ lavender: '\x1b[38;5;147m', // Model info, story IDs
58
+ softGold: '\x1b[38;5;222m', // Cost/money
59
+ teal: '\x1b[38;5;80m', // Pending states
60
+ slate: '\x1b[38;5;103m', // Secondary info
61
+ rose: '\x1b[38;5;211m', // Blocked/critical accent
62
+ amber: '\x1b[38;5;214m', // WIP/in-progress accent
63
+ powder: '\x1b[38;5;153m', // Labels/headers
64
+ };
65
+
66
+ // Background colors
67
+ const backgrounds = {
68
+ bgRed: '\x1b[41m',
69
+ bgGreen: '\x1b[42m',
70
+ bgYellow: '\x1b[43m',
71
+ bgBlue: '\x1b[44m',
72
+ };
73
+
74
+ // Brand color ANSI
75
+ const brand = '\x1b[38;2;232;104;58m';
76
+
77
+ // High-contrast mode colors (WCAG AAA)
78
+ const highContrast = {
79
+ red: '\x1b[91m',
80
+ green: '\x1b[92m',
81
+ yellow: '\x1b[93m',
82
+ blue: '\x1b[94m',
83
+ magenta: '\x1b[95m',
84
+ cyan: '\x1b[96m',
85
+ white: '\x1b[97m',
86
+ dim: '\x1b[0m',
87
+ brightBlack: '\x1b[37m',
88
+ brand: '\x1b[38;2;255;165;0m',
89
+ };
90
+
91
+ // Combined standard color palette
92
+ const cStandard = {
93
+ ...modifiers,
94
+ ...standard,
95
+ ...bright,
96
+ ...palette256,
97
+ ...backgrounds,
98
+ brand,
99
+ orange: brand, // Alias
100
+ // Semantic aliases
101
+ success: standard.green,
102
+ error: standard.red,
103
+ warning: standard.yellow,
104
+ info: standard.cyan,
105
+ };
106
+
107
+ module.exports = {
108
+ BRAND_HEX,
109
+ modifiers,
110
+ standard,
111
+ bright,
112
+ palette256,
113
+ backgrounds,
114
+ brand,
115
+ highContrast,
116
+ cStandard,
117
+ };
package/lib/colors.js CHANGED
@@ -4,6 +4,9 @@
4
4
  * Centralized ANSI color codes and formatting helpers.
5
5
  * Uses 256-color palette for modern terminal support.
6
6
  *
7
+ * Color definitions are generated from config/colors.yaml.
8
+ * Run: node scripts/generate-colors.js
9
+ *
7
10
  * WCAG AA Contrast Ratios (verified against #1a1a1a dark terminal background):
8
11
  * - Green (#32CD32): 4.5:1 ✓ (meets AA for normal text)
9
12
  * - Red (#FF6B6B): 5.0:1 ✓ (meets AA for normal text)
@@ -22,6 +25,9 @@
22
25
  * are for typical dark terminal configurations.
23
26
  */
24
27
 
28
+ // Import generated color definitions from YAML source of truth
29
+ const generated = require('./colors.generated');
30
+
25
31
  // High-contrast mode detection
26
32
  let _highContrastMode = null;
27
33
 
@@ -57,135 +63,79 @@ function resetHighContrast() {
57
63
  * Brand color hex value for chalk compatibility.
58
64
  * Use with chalk.hex(BRAND_HEX) in files that use chalk.
59
65
  */
60
- const BRAND_HEX = '#e8683a';
66
+ const { BRAND_HEX } = generated;
61
67
 
62
68
  /**
63
69
  * WCAG AAA high-contrast color palette (7:1+ contrast ratio).
64
70
  * Used when AGILEFLOW_HIGH_CONTRAST=1 or --high-contrast flag.
71
+ * Built from generated highContrast values plus additional mappings.
65
72
  */
66
73
  const hc = {
67
- // Reset and modifiers
68
- reset: '\x1b[0m',
69
- bold: '\x1b[1m',
70
- dim: '\x1b[0m', // No dimming in high-contrast (use regular text)
71
- italic: '\x1b[3m',
72
- underline: '\x1b[4m',
73
-
74
- // High-contrast standard colors (bright variants for max visibility)
75
- red: '\x1b[91m', // Bright red
76
- green: '\x1b[92m', // Bright green
77
- yellow: '\x1b[93m', // Bright yellow
78
- blue: '\x1b[94m', // Bright blue
79
- magenta: '\x1b[95m', // Bright magenta
80
- cyan: '\x1b[96m', // Bright cyan
81
- white: '\x1b[97m', // Bright white
74
+ // Reset and modifiers from generated
75
+ reset: generated.modifiers.reset,
76
+ bold: generated.modifiers.bold,
77
+ dim: generated.highContrast.dim, // No dimming in high-contrast
78
+ italic: generated.modifiers.italic,
79
+ underline: generated.modifiers.underline,
80
+
81
+ // High-contrast standard colors (from generated)
82
+ red: generated.highContrast.red,
83
+ green: generated.highContrast.green,
84
+ yellow: generated.highContrast.yellow,
85
+ blue: generated.highContrast.blue,
86
+ magenta: generated.highContrast.magenta,
87
+ cyan: generated.highContrast.cyan,
88
+ white: generated.highContrast.white,
82
89
 
83
90
  // Bright variants (same in high-contrast mode)
84
- brightBlack: '\x1b[37m', // Use white instead of gray
85
- brightRed: '\x1b[91m',
86
- brightGreen: '\x1b[92m',
87
- brightYellow: '\x1b[93m',
88
- brightBlue: '\x1b[94m',
89
- brightMagenta: '\x1b[95m',
90
- brightCyan: '\x1b[96m',
91
- brightWhite: '\x1b[97m',
91
+ brightBlack: generated.highContrast.brightBlack, // Use white instead of gray
92
+ brightRed: generated.highContrast.red,
93
+ brightGreen: generated.highContrast.green,
94
+ brightYellow: generated.highContrast.yellow,
95
+ brightBlue: generated.highContrast.blue,
96
+ brightMagenta: generated.highContrast.magenta,
97
+ brightCyan: generated.highContrast.cyan,
98
+ brightWhite: generated.highContrast.white,
92
99
 
93
100
  // 256-color high-contrast alternatives (all 7:1+ ratio)
94
- mintGreen: '\x1b[92m', // Bright green
95
- peach: '\x1b[93m', // Bright yellow
96
- coral: '\x1b[91m', // Bright red
97
- lightGreen: '\x1b[92m', // Bright green
98
- lightYellow: '\x1b[93m', // Bright yellow
99
- lightPink: '\x1b[91m', // Bright red
100
- skyBlue: '\x1b[96m', // Bright cyan
101
- lavender: '\x1b[95m', // Bright magenta
102
- softGold: '\x1b[93m', // Bright yellow
103
- teal: '\x1b[96m', // Bright cyan
104
- slate: '\x1b[97m', // White (instead of gray)
105
- rose: '\x1b[91m', // Bright red
106
- amber: '\x1b[93m', // Bright yellow
107
- powder: '\x1b[96m', // Bright cyan
108
-
109
- // Brand color - use bright orange/yellow for visibility
110
- brand: '\x1b[38;2;255;165;0m', // Bright orange (#FFA500 - 8.0:1 ratio)
111
- orange: '\x1b[38;2;255;165;0m',
101
+ mintGreen: generated.highContrast.green,
102
+ peach: generated.highContrast.yellow,
103
+ coral: generated.highContrast.red,
104
+ lightGreen: generated.highContrast.green,
105
+ lightYellow: generated.highContrast.yellow,
106
+ lightPink: generated.highContrast.red,
107
+ skyBlue: generated.highContrast.cyan,
108
+ lavender: generated.highContrast.magenta,
109
+ softGold: generated.highContrast.yellow,
110
+ teal: generated.highContrast.cyan,
111
+ slate: generated.highContrast.white, // White instead of gray
112
+ rose: generated.highContrast.red,
113
+ amber: generated.highContrast.yellow,
114
+ powder: generated.highContrast.cyan,
115
+
116
+ // Brand color - from generated high-contrast brand
117
+ brand: generated.highContrast.brand,
118
+ orange: generated.highContrast.brand,
112
119
 
113
120
  // Background colors (same as standard)
114
- bgRed: '\x1b[41m',
115
- bgGreen: '\x1b[42m',
116
- bgYellow: '\x1b[43m',
117
- bgBlue: '\x1b[44m',
121
+ bgRed: generated.backgrounds.bgRed,
122
+ bgGreen: generated.backgrounds.bgGreen,
123
+ bgYellow: generated.backgrounds.bgYellow,
124
+ bgBlue: generated.backgrounds.bgBlue,
118
125
 
119
126
  // Semantic aliases
120
- success: '\x1b[92m',
121
- error: '\x1b[91m',
122
- warning: '\x1b[93m',
123
- info: '\x1b[96m',
127
+ success: generated.highContrast.green,
128
+ error: generated.highContrast.red,
129
+ warning: generated.highContrast.yellow,
130
+ info: generated.highContrast.cyan,
124
131
  };
125
132
 
126
133
  /**
127
134
  * ANSI color codes for terminal output.
128
135
  * Includes standard colors, 256-color palette, and brand colors.
136
+ * Values imported from generated colors (config/colors.yaml source of truth).
129
137
  */
130
- const cStandard = {
131
- // Reset and modifiers
132
- reset: '\x1b[0m',
133
- bold: '\x1b[1m',
134
- dim: '\x1b[2m',
135
- italic: '\x1b[3m',
136
- underline: '\x1b[4m',
137
-
138
- // Standard ANSI colors (8 colors)
139
- red: '\x1b[31m',
140
- green: '\x1b[32m',
141
- yellow: '\x1b[33m',
142
- blue: '\x1b[34m',
143
- magenta: '\x1b[35m',
144
- cyan: '\x1b[36m',
145
- white: '\x1b[37m',
146
-
147
- // Bright variants
148
- brightBlack: '\x1b[90m',
149
- brightRed: '\x1b[91m',
150
- brightGreen: '\x1b[92m',
151
- brightYellow: '\x1b[93m',
152
- brightBlue: '\x1b[94m',
153
- brightMagenta: '\x1b[95m',
154
- brightCyan: '\x1b[96m',
155
- brightWhite: '\x1b[97m',
156
-
157
- // 256-color palette (vibrant, modern look)
158
- mintGreen: '\x1b[38;5;158m', // Healthy/success states
159
- peach: '\x1b[38;5;215m', // Warning states
160
- coral: '\x1b[38;5;203m', // Critical/error states
161
- lightGreen: '\x1b[38;5;194m', // Session healthy
162
- lightYellow: '\x1b[38;5;228m', // Session warning
163
- lightPink: '\x1b[38;5;210m', // Session critical
164
- skyBlue: '\x1b[38;5;117m', // Directories/paths, ready states
165
- lavender: '\x1b[38;5;147m', // Model info, story IDs
166
- softGold: '\x1b[38;5;222m', // Cost/money
167
- teal: '\x1b[38;5;80m', // Pending states
168
- slate: '\x1b[38;5;103m', // Secondary info
169
- rose: '\x1b[38;5;211m', // Blocked/critical accent
170
- amber: '\x1b[38;5;214m', // WIP/in-progress accent
171
- powder: '\x1b[38;5;153m', // Labels/headers
172
-
173
- // Brand color (#e8683a - burnt orange/terracotta)
174
- brand: '\x1b[38;2;232;104;58m',
175
- orange: '\x1b[38;2;232;104;58m', // Alias for brand color
176
-
177
- // Background colors
178
- bgRed: '\x1b[41m',
179
- bgGreen: '\x1b[42m',
180
- bgYellow: '\x1b[43m',
181
- bgBlue: '\x1b[44m',
182
-
183
- // Semantic aliases (for consistent meaning across codebase)
184
- success: '\x1b[32m', // Same as green
185
- error: '\x1b[31m', // Same as red
186
- warning: '\x1b[33m', // Same as yellow
187
- info: '\x1b[36m', // Same as cyan
188
- };
138
+ const cStandard = generated.cStandard;
189
139
 
190
140
  /**
191
141
  * Get the active color palette based on high-contrast mode.
@@ -0,0 +1,333 @@
1
+ /**
2
+ * GeneratorFactory - Dependency Injection for Content Generators
3
+ *
4
+ * Provides a centralized factory for creating content generators with:
5
+ * - Shared PlaceholderRegistry built once
6
+ * - Dependency injection for testability
7
+ * - Generator registration pattern
8
+ *
9
+ * Usage:
10
+ * const factory = new GeneratorFactory();
11
+ * factory.registerGenerator('help', HelpGenerator);
12
+ * factory.registerGenerator('readme', ReadmeGenerator);
13
+ *
14
+ * // Build registry once, pass to all generators
15
+ * const context = await factory.buildContext();
16
+ * await factory.runAll(context);
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const { PlaceholderRegistry, createDefaultRegistry } = require('./placeholder-registry');
22
+ const { createContainer, createScannerFactory } = require('./registry-di');
23
+
24
+ /**
25
+ * Generator interface
26
+ * @typedef {Object} IGenerator
27
+ * @property {string} name - Generator name
28
+ * @property {Function} register - Register placeholders: (registry) => void
29
+ * @property {Function} generate - Generate content: (context) => Promise<void>
30
+ */
31
+
32
+ /**
33
+ * GeneratorFactory - Factory for creating and running generators
34
+ */
35
+ class GeneratorFactory {
36
+ /**
37
+ * Create a new GeneratorFactory
38
+ * @param {Object} options - Factory options
39
+ * @param {Object} [options.container] - DI container (from registry-di.js)
40
+ * @param {PlaceholderRegistry} [options.registry] - Pre-built registry
41
+ * @param {Object} [options.paths] - Path configuration
42
+ */
43
+ constructor(options = {}) {
44
+ this._generators = new Map();
45
+ this._container = options.container || createContainer();
46
+ this._registry = options.registry || null;
47
+ this._paths = options.paths || {};
48
+ this._built = false;
49
+ this._context = null;
50
+ }
51
+
52
+ /**
53
+ * Register a generator with the factory
54
+ * @param {string} name - Generator name
55
+ * @param {IGenerator|Function} generator - Generator instance or class
56
+ * @returns {GeneratorFactory} this for chaining
57
+ */
58
+ registerGenerator(name, generator) {
59
+ if (!name || typeof name !== 'string') {
60
+ throw new Error('Generator name must be a non-empty string');
61
+ }
62
+
63
+ // Support both instances and classes
64
+ const instance = typeof generator === 'function' ? new generator() : generator;
65
+
66
+ if (!instance.register || typeof instance.register !== 'function') {
67
+ throw new Error(`Generator "${name}" must have a register(registry) method`);
68
+ }
69
+
70
+ if (!instance.generate || typeof instance.generate !== 'function') {
71
+ throw new Error(`Generator "${name}" must have a generate(context) method`);
72
+ }
73
+
74
+ this._generators.set(name, instance);
75
+
76
+ // Reset built state when new generator is added
77
+ this._built = false;
78
+ this._context = null;
79
+
80
+ return this;
81
+ }
82
+
83
+ /**
84
+ * Unregister a generator
85
+ * @param {string} name - Generator name
86
+ * @returns {boolean} True if removed
87
+ */
88
+ unregisterGenerator(name) {
89
+ const result = this._generators.delete(name);
90
+ if (result) {
91
+ this._built = false;
92
+ this._context = null;
93
+ }
94
+ return result;
95
+ }
96
+
97
+ /**
98
+ * Get list of registered generator names
99
+ * @returns {string[]}
100
+ */
101
+ getGeneratorNames() {
102
+ return Array.from(this._generators.keys());
103
+ }
104
+
105
+ /**
106
+ * Check if a generator is registered
107
+ * @param {string} name - Generator name
108
+ * @returns {boolean}
109
+ */
110
+ hasGenerator(name) {
111
+ return this._generators.has(name);
112
+ }
113
+
114
+ /**
115
+ * Get the shared registry instance
116
+ * @returns {PlaceholderRegistry}
117
+ */
118
+ getRegistry() {
119
+ if (!this._registry) {
120
+ this._registry = createDefaultRegistry();
121
+ }
122
+ return this._registry;
123
+ }
124
+
125
+ /**
126
+ * Build context by registering all generator placeholders
127
+ * Registry is built once and shared across all generators
128
+ * @param {Object} [baseContext={}] - Base context to merge
129
+ * @returns {Object} Built context
130
+ */
131
+ buildContext(baseContext = {}) {
132
+ if (this._built && this._context) {
133
+ return { ...this._context, ...baseContext };
134
+ }
135
+
136
+ const registry = this.getRegistry();
137
+
138
+ // Let each generator register its placeholders
139
+ for (const [name, generator] of this._generators) {
140
+ try {
141
+ generator.register(registry);
142
+ } catch (error) {
143
+ console.error(`Failed to register placeholders for "${name}":`, error.message);
144
+ }
145
+ }
146
+
147
+ // Build context with resolved values
148
+ this._context = {
149
+ registry,
150
+ container: this._container,
151
+ paths: this._paths,
152
+ scanner: createScannerFactory(this._container),
153
+ ...baseContext,
154
+ };
155
+
156
+ this._built = true;
157
+
158
+ return this._context;
159
+ }
160
+
161
+ /**
162
+ * Run a specific generator
163
+ * @param {string} name - Generator name
164
+ * @param {Object} [context] - Context override
165
+ * @returns {Promise<{success: boolean, error?: Error}>}
166
+ */
167
+ async runGenerator(name, context) {
168
+ const generator = this._generators.get(name);
169
+
170
+ if (!generator) {
171
+ return { success: false, error: new Error(`Generator not found: ${name}`) };
172
+ }
173
+
174
+ const ctx = context || this.buildContext();
175
+
176
+ try {
177
+ await generator.generate(ctx);
178
+ return { success: true };
179
+ } catch (error) {
180
+ return { success: false, error };
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Run all registered generators
186
+ * @param {Object} [context] - Context override
187
+ * @returns {Promise<Map<string, {success: boolean, error?: Error}>>}
188
+ */
189
+ async runAll(context) {
190
+ const ctx = context || this.buildContext();
191
+ const results = new Map();
192
+
193
+ for (const name of this._generators.keys()) {
194
+ results.set(name, await this.runGenerator(name, ctx));
195
+ }
196
+
197
+ return results;
198
+ }
199
+
200
+ /**
201
+ * Run generators in parallel
202
+ * @param {Object} [context] - Context override
203
+ * @returns {Promise<Map<string, {success: boolean, error?: Error}>>}
204
+ */
205
+ async runParallel(context) {
206
+ const ctx = context || this.buildContext();
207
+ const results = new Map();
208
+
209
+ const promises = Array.from(this._generators.keys()).map(async name => {
210
+ const result = await this.runGenerator(name, ctx);
211
+ return { name, result };
212
+ });
213
+
214
+ const settled = await Promise.allSettled(promises);
215
+
216
+ for (const item of settled) {
217
+ if (item.status === 'fulfilled') {
218
+ results.set(item.value.name, item.value.result);
219
+ } else {
220
+ // Shouldn't happen since runGenerator catches errors
221
+ const name = 'unknown';
222
+ results.set(name, { success: false, error: item.reason });
223
+ }
224
+ }
225
+
226
+ return results;
227
+ }
228
+
229
+ /**
230
+ * Reset the factory state
231
+ */
232
+ reset() {
233
+ this._built = false;
234
+ this._context = null;
235
+ this._registry = null;
236
+ }
237
+
238
+ /**
239
+ * Get container (for testing)
240
+ * @returns {Object}
241
+ */
242
+ getContainer() {
243
+ return this._container;
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Base generator class with common utilities
249
+ */
250
+ class BaseGenerator {
251
+ constructor(options = {}) {
252
+ this.name = options.name || this.constructor.name;
253
+ this.placeholders = [];
254
+ }
255
+
256
+ /**
257
+ * Register placeholders - override in subclass
258
+ * @param {PlaceholderRegistry} registry
259
+ */
260
+ register(registry) {
261
+ // Subclasses should override this method
262
+ for (const placeholder of this.placeholders) {
263
+ registry.register(placeholder.name, placeholder.resolver, placeholder.config);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Generate content - override in subclass
269
+ * @param {Object} context
270
+ * @returns {Promise<void>}
271
+ */
272
+ async generate(context) {
273
+ throw new Error('Subclass must implement generate(context)');
274
+ }
275
+
276
+ /**
277
+ * Helper to add a placeholder definition
278
+ * @param {string} name - Placeholder name
279
+ * @param {Function} resolver - Resolver function
280
+ * @param {Object} [config] - Configuration
281
+ */
282
+ addPlaceholder(name, resolver, config = {}) {
283
+ this.placeholders.push({ name, resolver, config });
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Create a factory with standard generators
289
+ * @param {Object} options - Factory options
290
+ * @returns {GeneratorFactory}
291
+ */
292
+ function createGeneratorFactory(options = {}) {
293
+ const factory = new GeneratorFactory(options);
294
+
295
+ // Register standard generators if provided
296
+ if (options.generators) {
297
+ for (const [name, generator] of Object.entries(options.generators)) {
298
+ factory.registerGenerator(name, generator);
299
+ }
300
+ }
301
+
302
+ return factory;
303
+ }
304
+
305
+ // Singleton instance
306
+ let _factoryInstance = null;
307
+
308
+ /**
309
+ * Get singleton factory instance
310
+ * @param {Object} [options] - Factory options
311
+ * @returns {GeneratorFactory}
312
+ */
313
+ function getGeneratorFactory(options = {}) {
314
+ if (!_factoryInstance || options.forceNew) {
315
+ _factoryInstance = createGeneratorFactory(options);
316
+ }
317
+ return _factoryInstance;
318
+ }
319
+
320
+ /**
321
+ * Reset singleton (for testing)
322
+ */
323
+ function resetGeneratorFactory() {
324
+ _factoryInstance = null;
325
+ }
326
+
327
+ module.exports = {
328
+ GeneratorFactory,
329
+ BaseGenerator,
330
+ createGeneratorFactory,
331
+ getGeneratorFactory,
332
+ resetGeneratorFactory,
333
+ };