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,371 @@
1
+ /**
2
+ * Wogi Flow - Config Substitution
3
+ *
4
+ * Enables dynamic values in config files using substitution patterns:
5
+ * - {env:VAR_NAME} - Environment variable substitution
6
+ * - {file:path/to/file} - File content substitution
7
+ *
8
+ * Features:
9
+ * - Tilde expansion (~/.secrets → /home/user/.secrets)
10
+ * - Nested object/array processing
11
+ * - Graceful handling of missing values
12
+ * - Warning logging for unresolved placeholders
13
+ *
14
+ * Usage:
15
+ * const { substituteConfig } = require('./.workflow/lib/config-substitution');
16
+ * const resolvedConfig = substituteConfig(rawConfig);
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const os = require('os');
22
+
23
+ // ============================================================
24
+ // Patterns
25
+ // ============================================================
26
+
27
+ /**
28
+ * Substitution patterns
29
+ */
30
+ const PATTERNS = {
31
+ env: /\{env:([^}]+)\}/g, // {env:VAR_NAME}
32
+ file: /\{file:([^}]+)\}/g // {file:path/to/file}
33
+ };
34
+
35
+ /**
36
+ * Check if a value contains substitution patterns
37
+ * @param {string} value - Value to check
38
+ * @returns {boolean}
39
+ */
40
+ function hasSubstitutionPattern(value) {
41
+ if (typeof value !== 'string') return false;
42
+ return PATTERNS.env.test(value) || PATTERNS.file.test(value);
43
+ }
44
+
45
+ // ============================================================
46
+ // Path Utilities
47
+ // ============================================================
48
+
49
+ /**
50
+ * Expand tilde (~) to user's home directory
51
+ * @param {string} filePath - Path that may contain ~
52
+ * @returns {string} Expanded path
53
+ */
54
+ function expandTilde(filePath) {
55
+ if (!filePath || typeof filePath !== 'string') return filePath;
56
+
57
+ if (filePath.startsWith('~/')) {
58
+ return path.join(os.homedir(), filePath.slice(2));
59
+ }
60
+ if (filePath === '~') {
61
+ return os.homedir();
62
+ }
63
+ return filePath;
64
+ }
65
+
66
+ /**
67
+ * Resolve a file path (handles tilde and relative paths)
68
+ * @param {string} filePath - Path to resolve
69
+ * @param {string} basePath - Base path for relative paths
70
+ * @returns {string} Resolved absolute path
71
+ */
72
+ function resolvePath(filePath, basePath = process.cwd()) {
73
+ const expanded = expandTilde(filePath);
74
+ if (path.isAbsolute(expanded)) {
75
+ return expanded;
76
+ }
77
+ return path.resolve(basePath, expanded);
78
+ }
79
+
80
+ // ============================================================
81
+ // Substitution Functions
82
+ // ============================================================
83
+
84
+ /**
85
+ * Substitute environment variable pattern
86
+ * @param {string} value - String containing {env:VAR} patterns
87
+ * @param {Object} options - Options
88
+ * @returns {Object} Result with value and warnings
89
+ */
90
+ function substituteEnvVars(value, options = {}) {
91
+ const { logWarnings = true } = options;
92
+ const warnings = [];
93
+
94
+ // Reset regex lastIndex
95
+ PATTERNS.env.lastIndex = 0;
96
+
97
+ const result = value.replace(PATTERNS.env, (match, varName) => {
98
+ const envValue = process.env[varName];
99
+
100
+ if (envValue !== undefined) {
101
+ return envValue;
102
+ }
103
+
104
+ // Env var not set - keep placeholder and warn
105
+ if (logWarnings) {
106
+ warnings.push({
107
+ type: 'env',
108
+ pattern: match,
109
+ variable: varName,
110
+ message: `Environment variable '${varName}' is not set`
111
+ });
112
+ }
113
+ return match; // Keep original placeholder
114
+ });
115
+
116
+ return { value: result, warnings };
117
+ }
118
+
119
+ /**
120
+ * Substitute file content pattern
121
+ * @param {string} value - String containing {file:path} patterns
122
+ * @param {Object} options - Options
123
+ * @returns {Object} Result with value and warnings
124
+ */
125
+ function substituteFileContents(value, options = {}) {
126
+ const { logWarnings = true, basePath = process.cwd() } = options;
127
+ const warnings = [];
128
+
129
+ // Reset regex lastIndex
130
+ PATTERNS.file.lastIndex = 0;
131
+
132
+ const result = value.replace(PATTERNS.file, (match, filePath) => {
133
+ const resolvedPath = resolvePath(filePath.trim(), basePath);
134
+
135
+ try {
136
+ if (fs.existsSync(resolvedPath)) {
137
+ const content = fs.readFileSync(resolvedPath, 'utf-8');
138
+ return content.trim(); // Trim whitespace from file contents
139
+ }
140
+
141
+ // File doesn't exist - keep placeholder and warn
142
+ if (logWarnings) {
143
+ warnings.push({
144
+ type: 'file',
145
+ pattern: match,
146
+ path: filePath,
147
+ resolvedPath,
148
+ message: `File not found: ${resolvedPath}`
149
+ });
150
+ }
151
+ return match; // Keep original placeholder
152
+ } catch (err) {
153
+ if (logWarnings) {
154
+ warnings.push({
155
+ type: 'file',
156
+ pattern: match,
157
+ path: filePath,
158
+ resolvedPath,
159
+ message: `Error reading file: ${err.message}`
160
+ });
161
+ }
162
+ return match;
163
+ }
164
+ });
165
+
166
+ return { value: result, warnings };
167
+ }
168
+
169
+ /**
170
+ * Substitute all patterns in a string value
171
+ * @param {string} value - String to process
172
+ * @param {Object} options - Options
173
+ * @returns {Object} Result with value and warnings
174
+ */
175
+ function substituteString(value, options = {}) {
176
+ if (typeof value !== 'string') {
177
+ return { value, warnings: [] };
178
+ }
179
+
180
+ const allWarnings = [];
181
+
182
+ // First pass: environment variables
183
+ let result = value;
184
+ const envResult = substituteEnvVars(result, options);
185
+ result = envResult.value;
186
+ allWarnings.push(...envResult.warnings);
187
+
188
+ // Second pass: file contents
189
+ const fileResult = substituteFileContents(result, options);
190
+ result = fileResult.value;
191
+ allWarnings.push(...fileResult.warnings);
192
+
193
+ return { value: result, warnings: allWarnings };
194
+ }
195
+
196
+ // ============================================================
197
+ // Deep Substitution
198
+ // ============================================================
199
+
200
+ /**
201
+ * Recursively substitute patterns in an object/array
202
+ * @param {*} obj - Object, array, or value to process
203
+ * @param {Object} options - Options
204
+ * @param {string} path - Current path in object (for debugging)
205
+ * @returns {Object} Result with value and warnings
206
+ */
207
+ function substituteDeep(obj, options = {}, currentPath = '') {
208
+ const allWarnings = [];
209
+
210
+ if (obj === null || obj === undefined) {
211
+ return { value: obj, warnings: [] };
212
+ }
213
+
214
+ // Handle arrays
215
+ if (Array.isArray(obj)) {
216
+ const result = obj.map((item, index) => {
217
+ const itemResult = substituteDeep(item, options, `${currentPath}[${index}]`);
218
+ allWarnings.push(...itemResult.warnings.map(w => ({
219
+ ...w,
220
+ path: `${currentPath}[${index}]`
221
+ })));
222
+ return itemResult.value;
223
+ });
224
+ return { value: result, warnings: allWarnings };
225
+ }
226
+
227
+ // Handle objects
228
+ if (typeof obj === 'object') {
229
+ const result = {};
230
+ for (const [key, value] of Object.entries(obj)) {
231
+ const keyPath = currentPath ? `${currentPath}.${key}` : key;
232
+ const itemResult = substituteDeep(value, options, keyPath);
233
+ allWarnings.push(...itemResult.warnings.map(w => ({
234
+ ...w,
235
+ path: keyPath
236
+ })));
237
+ result[key] = itemResult.value;
238
+ }
239
+ return { value: result, warnings: allWarnings };
240
+ }
241
+
242
+ // Handle strings
243
+ if (typeof obj === 'string') {
244
+ const strResult = substituteString(obj, options);
245
+ return {
246
+ value: strResult.value,
247
+ warnings: strResult.warnings.map(w => ({
248
+ ...w,
249
+ path: currentPath
250
+ }))
251
+ };
252
+ }
253
+
254
+ // Other primitives - return as-is
255
+ return { value: obj, warnings: [] };
256
+ }
257
+
258
+ // ============================================================
259
+ // Main API
260
+ // ============================================================
261
+
262
+ /**
263
+ * Substitute all patterns in a config object
264
+ * @param {Object} config - Config object to process
265
+ * @param {Object} options - Options
266
+ * @param {boolean} options.logWarnings - Whether to log warnings (default: true)
267
+ * @param {boolean} options.printWarnings - Whether to print warnings to console (default: false)
268
+ * @param {string} options.basePath - Base path for relative file paths
269
+ * @returns {Object} Result with value and warnings array
270
+ */
271
+ function substituteConfig(config, options = {}) {
272
+ const { printWarnings = false, logWarnings = true, basePath } = options;
273
+
274
+ const result = substituteDeep(config, { logWarnings, basePath });
275
+
276
+ // Print warnings to console if requested
277
+ if (printWarnings && result.warnings.length > 0) {
278
+ console.warn('\n⚠️ Config substitution warnings:');
279
+ for (const warning of result.warnings) {
280
+ const pathInfo = warning.path ? ` at '${warning.path}'` : '';
281
+ console.warn(` - ${warning.message}${pathInfo}`);
282
+ }
283
+ console.warn('');
284
+ }
285
+
286
+ return result;
287
+ }
288
+
289
+ /**
290
+ * Check if a config has unresolved substitution patterns
291
+ * @param {Object} config - Config object to check
292
+ * @returns {Object} Result with hasUnresolved boolean and patterns array
293
+ */
294
+ function checkUnresolvedPatterns(config) {
295
+ const configStr = JSON.stringify(config);
296
+ const unresolvedEnv = [];
297
+ const unresolvedFile = [];
298
+
299
+ // Check for env patterns
300
+ PATTERNS.env.lastIndex = 0;
301
+ let match;
302
+ while ((match = PATTERNS.env.exec(configStr)) !== null) {
303
+ unresolvedEnv.push({ pattern: match[0], variable: match[1] });
304
+ }
305
+
306
+ // Check for file patterns
307
+ PATTERNS.file.lastIndex = 0;
308
+ while ((match = PATTERNS.file.exec(configStr)) !== null) {
309
+ unresolvedFile.push({ pattern: match[0], path: match[1] });
310
+ }
311
+
312
+ return {
313
+ hasUnresolved: unresolvedEnv.length > 0 || unresolvedFile.length > 0,
314
+ env: unresolvedEnv,
315
+ file: unresolvedFile
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Get a list of all substitution patterns used in a config
321
+ * @param {Object} config - Config object to scan
322
+ * @returns {Object} Object with env and file pattern lists
323
+ */
324
+ function getUsedPatterns(config) {
325
+ const configStr = JSON.stringify(config);
326
+ const envPatterns = new Set();
327
+ const filePatterns = new Set();
328
+
329
+ // Find env patterns
330
+ PATTERNS.env.lastIndex = 0;
331
+ let match;
332
+ while ((match = PATTERNS.env.exec(configStr)) !== null) {
333
+ envPatterns.add(match[1]);
334
+ }
335
+
336
+ // Find file patterns
337
+ PATTERNS.file.lastIndex = 0;
338
+ while ((match = PATTERNS.file.exec(configStr)) !== null) {
339
+ filePatterns.add(match[1]);
340
+ }
341
+
342
+ return {
343
+ env: Array.from(envPatterns),
344
+ file: Array.from(filePatterns)
345
+ };
346
+ }
347
+
348
+ // ============================================================
349
+ // Exports
350
+ // ============================================================
351
+
352
+ module.exports = {
353
+ // Main API
354
+ substituteConfig,
355
+ checkUnresolvedPatterns,
356
+ getUsedPatterns,
357
+
358
+ // Lower-level functions
359
+ substituteString,
360
+ substituteEnvVars,
361
+ substituteFileContents,
362
+ substituteDeep,
363
+
364
+ // Utilities
365
+ expandTilde,
366
+ resolvePath,
367
+ hasSubstitutionPattern,
368
+
369
+ // Constants
370
+ PATTERNS
371
+ };