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,481 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Security Utilities
5
+ *
6
+ * Shared security functions for safe command execution and path validation.
7
+ * Part of Phase 1: Critical Security Fixes (wf-9bcb4fa8)
8
+ *
9
+ * Functions:
10
+ * - validatePathWithinProject: Prevent path traversal attacks
11
+ * - safeExecFile: Execute commands safely without shell injection
12
+ * - safeGitCommand: Execute git commands with validated arguments
13
+ * - escapeRegex: Escape regex special characters for safe patterns
14
+ * - validateGitRef: Validate git branch/tag names
15
+ * - validateRepoFormat: Validate GitHub repository format
16
+ */
17
+
18
+ const { execFileSync, spawnSync } = require('child_process');
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ // ============================================================
23
+ // Constants
24
+ // ============================================================
25
+
26
+ /** Maximum length for regex patterns to prevent ReDoS */
27
+ const MAX_REGEX_LENGTH = 100;
28
+
29
+ /** Allowed characters in git branch names (per git-check-ref-format) */
30
+ const GIT_REF_PATTERN = /^[a-zA-Z0-9_\-./]+$/;
31
+
32
+ /** GitHub repository format: owner/repo */
33
+ const GITHUB_REPO_PATTERN = /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+$/;
34
+
35
+ /** Valid file extensions for code search */
36
+ const VALID_CODE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
37
+
38
+ // ============================================================
39
+ // Path Validation
40
+ // ============================================================
41
+
42
+ /**
43
+ * Validate that a path is within the project root directory.
44
+ * Prevents path traversal attacks using ../ or absolute paths.
45
+ *
46
+ * @param {string} filePath - Path to validate
47
+ * @param {string} projectRoot - Project root directory
48
+ * @returns {boolean} True if path is safely within project root
49
+ */
50
+ function validatePathWithinProject(filePath, projectRoot) {
51
+ if (!filePath || !projectRoot) {
52
+ return false;
53
+ }
54
+
55
+ // Resolve to absolute path
56
+ const resolvedPath = path.resolve(projectRoot, filePath);
57
+
58
+ // Get real path if it exists (resolves symlinks)
59
+ let realPath = resolvedPath;
60
+ try {
61
+ if (fs.existsSync(resolvedPath)) {
62
+ realPath = fs.realpathSync(resolvedPath);
63
+ }
64
+ } catch (err) {
65
+ // If realpathSync fails, continue with resolved path
66
+ if (process.env.DEBUG) console.warn(`[Security] realpathSync failed for ${resolvedPath}: ${err.message}`);
67
+ }
68
+
69
+ // Get real project root (resolves symlinks)
70
+ let realProjectRoot = projectRoot;
71
+ try {
72
+ realProjectRoot = fs.realpathSync(projectRoot);
73
+ } catch (err) {
74
+ // If realpathSync fails, continue with original
75
+ if (process.env.DEBUG) console.warn(`[Security] realpathSync failed for projectRoot: ${err.message}`);
76
+ }
77
+
78
+ // Ensure path starts with project root + separator
79
+ // The separator check prevents /project/foo matching /project-other/foo
80
+ return realPath === realProjectRoot ||
81
+ realPath.startsWith(realProjectRoot + path.sep);
82
+ }
83
+
84
+ /**
85
+ * Sanitize a path for safe use in file operations.
86
+ * Returns null if path is invalid or traverses outside project.
87
+ *
88
+ * @param {string} filePath - Path to sanitize
89
+ * @param {string} projectRoot - Project root directory
90
+ * @returns {string|null} Sanitized absolute path or null if invalid
91
+ */
92
+ function sanitizePath(filePath, projectRoot) {
93
+ if (!filePath || typeof filePath !== 'string') {
94
+ return null;
95
+ }
96
+
97
+ // Resolve path relative to project root
98
+ const resolved = path.resolve(projectRoot, filePath);
99
+
100
+ // Validate it's within project
101
+ if (!validatePathWithinProject(resolved, projectRoot)) {
102
+ return null;
103
+ }
104
+
105
+ return resolved;
106
+ }
107
+
108
+ // ============================================================
109
+ // Safe Command Execution
110
+ // ============================================================
111
+
112
+ /**
113
+ * Execute a command safely using execFileSync (no shell).
114
+ * Prevents command injection by passing arguments as an array.
115
+ *
116
+ * @param {string} command - Command to execute (e.g., 'git', 'grep')
117
+ * @param {string[]} args - Array of command arguments
118
+ * @param {Object} [options] - execFileSync options
119
+ * @param {string} [options.cwd] - Working directory
120
+ * @param {number} [options.timeout] - Timeout in milliseconds
121
+ * @param {string} [options.encoding] - Output encoding (default: 'utf-8')
122
+ * @returns {string} Command output
123
+ * @throws {Error} If command fails
124
+ */
125
+ function safeExecFile(command, args, options = {}) {
126
+ const {
127
+ cwd = process.cwd(),
128
+ timeout = 30000,
129
+ encoding = 'utf-8',
130
+ ...rest
131
+ } = options;
132
+
133
+ return execFileSync(command, args, {
134
+ cwd,
135
+ timeout,
136
+ encoding,
137
+ stdio: ['pipe', 'pipe', 'pipe'],
138
+ ...rest
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Execute a command safely using spawnSync (no shell).
144
+ * Useful for commands that need more control over output handling.
145
+ *
146
+ * @param {string} command - Command to execute
147
+ * @param {string[]} args - Array of command arguments
148
+ * @param {Object} [options] - spawnSync options
149
+ * @returns {Object} spawnSync result { status, stdout, stderr, error }
150
+ */
151
+ function safeSpawn(command, args, options = {}) {
152
+ const {
153
+ cwd = process.cwd(),
154
+ timeout = 30000,
155
+ encoding = 'utf-8',
156
+ ...rest
157
+ } = options;
158
+
159
+ return spawnSync(command, args, {
160
+ cwd,
161
+ timeout,
162
+ encoding,
163
+ ...rest
164
+ });
165
+ }
166
+
167
+ // ============================================================
168
+ // Git Command Safety
169
+ // ============================================================
170
+
171
+ /**
172
+ * Validate a git reference name (branch, tag, commit).
173
+ * Based on git-check-ref-format rules.
174
+ *
175
+ * @param {string} ref - Git reference to validate
176
+ * @returns {boolean} True if valid git reference
177
+ */
178
+ function validateGitRef(ref) {
179
+ if (!ref || typeof ref !== 'string') {
180
+ return false;
181
+ }
182
+
183
+ // Length check
184
+ if (ref.length === 0 || ref.length > 255) {
185
+ return false;
186
+ }
187
+
188
+ // Cannot start or end with dot, cannot contain ..
189
+ if (ref.startsWith('.') || ref.endsWith('.') || ref.includes('..')) {
190
+ return false;
191
+ }
192
+
193
+ // Cannot contain certain characters
194
+ if (ref.includes(' ') || ref.includes('~') || ref.includes('^') ||
195
+ ref.includes(':') || ref.includes('?') || ref.includes('*') ||
196
+ ref.includes('[') || ref.includes('\\') || ref.includes('@{')) {
197
+ return false;
198
+ }
199
+
200
+ // Must match allowed pattern
201
+ return GIT_REF_PATTERN.test(ref);
202
+ }
203
+
204
+ /**
205
+ * Execute a git command with validated arguments.
206
+ * Prevents injection through git arguments.
207
+ *
208
+ * @param {string[]} args - Git subcommand and arguments as array
209
+ * @param {Object} [options] - Execution options
210
+ * @param {string} [options.cwd] - Working directory
211
+ * @param {boolean} [options.silent] - Suppress errors
212
+ * @returns {string|null} Command output or null if failed with silent=true
213
+ */
214
+ function safeGitCommand(args, options = {}) {
215
+ const { cwd = process.cwd(), silent = false, timeout = 30000 } = options;
216
+
217
+ try {
218
+ const result = execFileSync('git', args, {
219
+ cwd,
220
+ encoding: 'utf-8',
221
+ timeout,
222
+ stdio: ['pipe', 'pipe', 'pipe']
223
+ });
224
+ return result.trim();
225
+ } catch (err) {
226
+ if (!silent) {
227
+ throw new Error(`Git command failed: git ${args.join(' ')}\n${err.stderr || err.message}`);
228
+ }
229
+ return null;
230
+ }
231
+ }
232
+
233
+ // ============================================================
234
+ // Safe Grep/Search
235
+ // ============================================================
236
+
237
+ /**
238
+ * Escape special regex characters for use in patterns.
239
+ *
240
+ * @param {string} str - String to escape
241
+ * @returns {string} Escaped string safe for regex
242
+ */
243
+ function escapeRegex(str) {
244
+ if (!str || typeof str !== 'string') {
245
+ return '';
246
+ }
247
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
248
+ }
249
+
250
+ /**
251
+ * Validate and sanitize a search pattern.
252
+ * Escapes special characters and limits length.
253
+ *
254
+ * @param {string} pattern - Search pattern
255
+ * @param {Object} [options]
256
+ * @param {number} [options.maxLength] - Maximum pattern length
257
+ * @param {boolean} [options.escape] - Whether to escape regex chars
258
+ * @returns {string|null} Sanitized pattern or null if invalid
259
+ */
260
+ function sanitizeSearchPattern(pattern, options = {}) {
261
+ const {
262
+ maxLength = MAX_REGEX_LENGTH,
263
+ escape = true
264
+ } = options;
265
+
266
+ if (!pattern || typeof pattern !== 'string') {
267
+ return null;
268
+ }
269
+
270
+ // Limit length to prevent ReDoS
271
+ if (pattern.length > maxLength) {
272
+ return null;
273
+ }
274
+
275
+ // Optionally escape regex special characters
276
+ return escape ? escapeRegex(pattern) : pattern;
277
+ }
278
+
279
+ /**
280
+ * Execute grep safely using execFileSync.
281
+ * Escapes pattern to prevent regex injection.
282
+ *
283
+ * @param {string} pattern - Search pattern (will be escaped)
284
+ * @param {Object} options
285
+ * @param {string} options.cwd - Working directory
286
+ * @param {string} options.searchDir - Directory to search in
287
+ * @param {string[]} [options.extensions] - File extensions to include
288
+ * @param {number} [options.maxResults] - Maximum results to return
289
+ * @returns {string[]} Array of matching file paths
290
+ */
291
+ function safeGrep(pattern, options = {}) {
292
+ const {
293
+ cwd,
294
+ searchDir,
295
+ extensions = VALID_CODE_EXTENSIONS,
296
+ maxResults = 20
297
+ } = options;
298
+
299
+ // Validate and escape pattern
300
+ const safePattern = sanitizeSearchPattern(pattern);
301
+ if (!safePattern) {
302
+ return [];
303
+ }
304
+
305
+ // Build include arguments
306
+ const includeArgs = extensions.flatMap(ext => ['--include', `*${ext}`]);
307
+
308
+ try {
309
+ const args = [
310
+ '-ril', // recursive, ignore case, files only
311
+ safePattern, // escaped pattern
312
+ ...includeArgs, // file extensions
313
+ searchDir // search directory
314
+ ];
315
+
316
+ const output = safeExecFile('grep', args, {
317
+ cwd,
318
+ timeout: 5000
319
+ });
320
+
321
+ return output
322
+ .split('\n')
323
+ .filter(Boolean)
324
+ .slice(0, maxResults);
325
+ } catch (err) {
326
+ // grep returns non-zero when no matches found - this is expected
327
+ if (process.env.DEBUG && err.status !== 1) {
328
+ console.warn(`[Security] safeGrep failed: ${err.message}`);
329
+ }
330
+ return [];
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Execute find safely using execFileSync.
336
+ *
337
+ * @param {string} dir - Directory to search
338
+ * @param {Object} options
339
+ * @param {string[]} [options.extensions] - File extensions to find
340
+ * @param {number} [options.maxResults] - Maximum results
341
+ * @param {string} [options.cwd] - Working directory
342
+ * @returns {string[]} Array of found file paths
343
+ */
344
+ function safeFind(dir, options = {}) {
345
+ const {
346
+ extensions = VALID_CODE_EXTENSIONS,
347
+ maxResults = 100,
348
+ cwd = process.cwd()
349
+ } = options;
350
+
351
+ // Build name expressions for extensions
352
+ const nameArgs = [];
353
+ extensions.forEach((ext, i) => {
354
+ if (i > 0) nameArgs.push('-o');
355
+ nameArgs.push('-name', `*${ext}`);
356
+ });
357
+
358
+ try {
359
+ const args = [
360
+ dir,
361
+ '-type', 'f',
362
+ '(', ...nameArgs, ')'
363
+ ];
364
+
365
+ const output = safeExecFile('find', args, { cwd, timeout: 10000 });
366
+
367
+ return output
368
+ .split('\n')
369
+ .filter(Boolean)
370
+ .slice(0, maxResults);
371
+ } catch (err) {
372
+ if (process.env.DEBUG) {
373
+ console.warn(`[Security] safeFind failed: ${err.message}`);
374
+ }
375
+ return [];
376
+ }
377
+ }
378
+
379
+ // ============================================================
380
+ // GitHub/Repository Validation
381
+ // ============================================================
382
+
383
+ /**
384
+ * Validate GitHub repository format (owner/repo).
385
+ *
386
+ * @param {string} repo - Repository string to validate
387
+ * @returns {boolean} True if valid format
388
+ */
389
+ function validateRepoFormat(repo) {
390
+ if (!repo || typeof repo !== 'string') {
391
+ return false;
392
+ }
393
+ return GITHUB_REPO_PATTERN.test(repo);
394
+ }
395
+
396
+ /**
397
+ * Sanitize a commit message for safe use in git commands.
398
+ * Escapes special characters that could cause issues.
399
+ *
400
+ * @param {string} message - Commit message to sanitize
401
+ * @returns {string} Sanitized message
402
+ */
403
+ function sanitizeCommitMessage(message) {
404
+ if (!message || typeof message !== 'string') {
405
+ return '';
406
+ }
407
+ // Only allow basic text characters, newlines, and common punctuation
408
+ return message
409
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '') // Remove control chars
410
+ .substring(0, 5000); // Limit length
411
+ }
412
+
413
+ // ============================================================
414
+ // URL Validation
415
+ // ============================================================
416
+
417
+ /**
418
+ * Check if an IP address is private/internal.
419
+ * Used for SSRF protection.
420
+ *
421
+ * @param {string} ip - IP address to check
422
+ * @returns {boolean} True if private/internal IP
423
+ */
424
+ function isPrivateIP(ip) {
425
+ if (!ip) return false;
426
+
427
+ // IPv4 private ranges
428
+ const parts = ip.split('.').map(Number);
429
+ if (parts.length === 4 && parts.every(p => !isNaN(p))) {
430
+ // Loopback
431
+ if (parts[0] === 127) return true;
432
+ // 10.0.0.0/8
433
+ if (parts[0] === 10) return true;
434
+ // 172.16.0.0/12
435
+ if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
436
+ // 192.168.0.0/16
437
+ if (parts[0] === 192 && parts[1] === 168) return true;
438
+ // Link-local
439
+ if (parts[0] === 169 && parts[1] === 254) return true;
440
+ }
441
+
442
+ // Localhost
443
+ if (ip === 'localhost' || ip === '::1') return true;
444
+
445
+ return false;
446
+ }
447
+
448
+ // ============================================================
449
+ // Exports
450
+ // ============================================================
451
+
452
+ module.exports = {
453
+ // Path validation
454
+ validatePathWithinProject,
455
+ sanitizePath,
456
+
457
+ // Command execution
458
+ safeExecFile,
459
+ safeSpawn,
460
+
461
+ // Git safety
462
+ validateGitRef,
463
+ safeGitCommand,
464
+ sanitizeCommitMessage,
465
+
466
+ // Search safety
467
+ escapeRegex,
468
+ sanitizeSearchPattern,
469
+ safeGrep,
470
+ safeFind,
471
+
472
+ // Repository validation
473
+ validateRepoFormat,
474
+
475
+ // URL/Network safety
476
+ isPrivateIP,
477
+
478
+ // Constants
479
+ MAX_REGEX_LENGTH,
480
+ VALID_CODE_EXTENSIONS
481
+ };
@@ -0,0 +1,106 @@
1
+ #!/bin/bash
2
+
3
+ # Wogi Flow - End Session Properly
4
+
5
+ set -e
6
+
7
+ WORKFLOW_DIR=".workflow"
8
+
9
+ # Colors
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ CYAN='\033[0;36m'
13
+ RED='\033[0;31m'
14
+ NC='\033[0m'
15
+
16
+ echo -e "${CYAN}Ending Session${NC}"
17
+ echo "==============="
18
+ echo ""
19
+
20
+ # Check config for onSessionEnd requirements
21
+ if [ -f "$WORKFLOW_DIR/config.json" ]; then
22
+ echo -e "${YELLOW}Checking session-end requirements...${NC}"
23
+ CONFIG_FILE="$WORKFLOW_DIR/config.json" python3 << 'EOF'
24
+ import json, os
25
+ with open(os.environ['CONFIG_FILE']) as f:
26
+ config = json.load(f)
27
+ steps = config.get('mandatorySteps', {}).get('onSessionEnd', [])
28
+ if steps:
29
+ print('Required:')
30
+ for step in steps:
31
+ print(f' • {step}')
32
+ EOF
33
+ echo ""
34
+ fi
35
+
36
+ # Check for uncommitted changes
37
+ uncommitted=$(git status --porcelain 2>/dev/null | wc -l)
38
+ if [ $uncommitted -gt 0 ]; then
39
+ echo -e "${YELLOW}Uncommitted changes: $uncommitted files${NC}"
40
+ git status --short
41
+ echo ""
42
+
43
+ read -p "Commit all changes? (y/N) " confirm
44
+ if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
45
+ read -p "Commit message: " msg
46
+ git add -A
47
+ git commit -m "${msg:-checkpoint: end of session}"
48
+ echo -e "${GREEN}✓ Changes committed${NC}"
49
+ fi
50
+ else
51
+ echo -e "${GREEN}✓ No uncommitted changes${NC}"
52
+ fi
53
+
54
+ echo ""
55
+
56
+ # Update progress.md
57
+ if [ -f "$WORKFLOW_DIR/state/progress.md" ]; then
58
+ echo -e "${YELLOW}Updating progress.md...${NC}"
59
+
60
+ # Create backup
61
+ cp "$WORKFLOW_DIR/state/progress.md" "$WORKFLOW_DIR/state/progress.md.bak"
62
+
63
+ # Update timestamp
64
+ sed -i.tmp "s/## Last Updated.*/## Last Updated\n$(date +%Y-%m-%d\ %H:%M)/" "$WORKFLOW_DIR/state/progress.md" 2>/dev/null || true
65
+ rm -f "$WORKFLOW_DIR/state/progress.md.tmp"
66
+
67
+ echo -e "${GREEN}✓ Progress updated${NC}"
68
+ fi
69
+
70
+ # Extract skill learnings
71
+ if [ -f "$WORKFLOW_DIR/config.json" ]; then
72
+ skill_learning=$(CONFIG_FILE="$WORKFLOW_DIR/config.json" python3 -c "
73
+ import json, os
74
+ with open(os.environ['CONFIG_FILE']) as f:
75
+ config = json.load(f)
76
+ sl = config.get('skillLearning', {})
77
+ enabled = sl.get('enabled', False) and sl.get('autoExtract', False)
78
+ print('True' if enabled else 'False')
79
+ " 2>/dev/null || echo "False")
80
+
81
+ if [ "$skill_learning" = "True" ]; then
82
+ echo ""
83
+ echo -e "${YELLOW}Extracting skill learnings...${NC}"
84
+ if command -v node &> /dev/null && [ -f "scripts/flow-skill-learn.js" ]; then
85
+ node scripts/flow-skill-learn.js --trigger=session-end 2>/dev/null || true
86
+ echo -e "${GREEN}✓ Skills updated${NC}"
87
+ fi
88
+ fi
89
+ fi
90
+
91
+ echo ""
92
+
93
+ # Push if remote exists
94
+ if git remote get-url origin &>/dev/null; then
95
+ read -p "Push to remote? (y/N) " push_confirm
96
+ if [ "$push_confirm" = "y" ] || [ "$push_confirm" = "Y" ]; then
97
+ git push
98
+ echo -e "${GREEN}✓ Pushed to remote${NC}"
99
+ fi
100
+ fi
101
+
102
+ echo ""
103
+ echo -e "${GREEN}Session ended cleanly.${NC}"
104
+ echo ""
105
+ echo "Summary:"
106
+ ./scripts/flow status 2>/dev/null || echo " (run 'flow status' for details)"