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,985 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Damage Control System
5
+ *
6
+ * Event-based pattern matching for destructive command protection.
7
+ * Supports multiple event types: bash, file, stop, prompt.
8
+ *
9
+ * Inspired by Hookify plugin patterns, adapted for multi-CLI compatibility.
10
+ *
11
+ * Usage:
12
+ * flow damage-control check "<command>" Check if command is allowed
13
+ * flow damage-control event <type> <ctx> Check event against rules
14
+ * flow damage-control status Show damage control status
15
+ * flow damage-control rules Show all rules
16
+ * flow dc check "rm -rf node_modules" Shorthand
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { getProjectRoot, colors, getConfig } = require('./flow-utils');
22
+
23
+ const PROJECT_ROOT = getProjectRoot();
24
+ const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
25
+ const PATTERNS_FILE = path.join(WORKFLOW_DIR, 'damage-control.yaml');
26
+
27
+ // ============================================================
28
+ // Event Types and Actions
29
+ // ============================================================
30
+
31
+ const EVENT_TYPES = ['bash', 'file', 'stop', 'prompt', 'all'];
32
+ const ACTIONS = ['block', 'warn', 'ask', 'allow'];
33
+
34
+ // Maximum allowed regex pattern length to prevent abuse
35
+ // Reduced from 500 to prevent ReDoS attacks with complex patterns
36
+ const MAX_REGEX_LENGTH = 100;
37
+
38
+ // Maximum input length to test against regex (prevents slow matching)
39
+ const MAX_INPUT_LENGTH = 10000;
40
+
41
+ /**
42
+ * Create a RegExp safely, rejecting patterns that could cause ReDoS
43
+ * @param {string} pattern - The regex pattern string
44
+ * @param {string} flags - Optional regex flags
45
+ * @returns {RegExp|null} - Compiled regex or null if unsafe/invalid
46
+ */
47
+ function safeRegExp(pattern, flags = '') {
48
+ // Reject overly long patterns
49
+ if (pattern.length > MAX_REGEX_LENGTH) {
50
+ console.error(`Regex pattern too long (${pattern.length} > ${MAX_REGEX_LENGTH}): ${pattern.substring(0, 50)}...`);
51
+ return null;
52
+ }
53
+
54
+ // Check for common ReDoS patterns (nested quantifiers)
55
+ // These patterns can cause exponential backtracking
56
+ const redosPatterns = [
57
+ /\([^)]*\+[^)]*\)\+/, // (a+)+ nested quantifiers
58
+ /\([^)]*\*[^)]*\)\+/, // (a*)+
59
+ /\([^)]*\+[^)]*\)\*/, // (a+)*
60
+ /\([^)]*\*[^)]*\)\*/, // (a*)*
61
+ /\([^)]*\+[^)]*\)\{/, // (a+){n}
62
+ /\([^)]*\*[^)]*\)\{/, // (a*){n}
63
+ /\.\*\.\*/, // .*.* greedy wildcards
64
+ /\.\+\.\+/, // .+.+ greedy wildcards
65
+ /\([^)]*\|[^)]*\)\+/, // (a|b)+ alternation with quantifier
66
+ ];
67
+
68
+ for (const redos of redosPatterns) {
69
+ if (redos.test(pattern)) {
70
+ console.error(`Potentially unsafe regex pattern (ReDoS risk): ${pattern}`);
71
+ return null;
72
+ }
73
+ }
74
+
75
+ try {
76
+ return new RegExp(pattern, flags);
77
+ } catch (err) {
78
+ console.error(`Invalid regex pattern: ${pattern} - ${err.message}`);
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Safely test a regex against input with length limits
85
+ * @param {RegExp} regex - The compiled regex
86
+ * @param {string} input - The input string to test
87
+ * @returns {boolean} - True if matches, false otherwise
88
+ */
89
+ function safeRegexTest(regex, input) {
90
+ if (!regex) return false;
91
+
92
+ // Truncate overly long inputs to prevent slow matching
93
+ const safeInput = typeof input === 'string' && input.length > MAX_INPUT_LENGTH
94
+ ? input.substring(0, MAX_INPUT_LENGTH)
95
+ : String(input);
96
+
97
+ return regex.test(safeInput);
98
+ }
99
+
100
+ function log(color, ...args) {
101
+ console.log(colors[color] + args.join(' ') + colors.reset);
102
+ }
103
+
104
+ // Commands that are always safe (read-only)
105
+ const SAFE_COMMANDS = [
106
+ /^ls\b/,
107
+ /^cat\b/,
108
+ /^head\b/,
109
+ /^tail\b/,
110
+ /^grep\b/,
111
+ /^rg\b/,
112
+ /^find\b/,
113
+ /^git\s+(status|log|diff|branch|show|remote|tag)\b/,
114
+ /^npm\s+(test|run|list|ls|view|search|info)\b/,
115
+ /^node\s+--check\b/,
116
+ /^node\s+-c\b/,
117
+ /^echo\b/,
118
+ /^pwd\b/,
119
+ /^which\b/,
120
+ /^type\b/,
121
+ /^whoami\b/,
122
+ /^hostname\b/,
123
+ /^date\b/,
124
+ /^wc\b/,
125
+ /^sort\b/,
126
+ /^uniq\b/,
127
+ /^diff\b/,
128
+ /^file\b/,
129
+ /^tree\b/,
130
+ /^du\b/,
131
+ /^df\b/
132
+ ];
133
+
134
+ /**
135
+ * Process YAML escape sequences in double-quoted strings
136
+ * Handles: \\ -> \, \n -> newline, \t -> tab, \" -> "
137
+ */
138
+ function processYamlEscapes(str) {
139
+ return str
140
+ .replace(/\\\\/g, '\x00') // Temporarily replace \\ with placeholder
141
+ .replace(/\\n/g, '\n')
142
+ .replace(/\\t/g, '\t')
143
+ .replace(/\\"/g, '"')
144
+ .replace(/\x00/g, '\\'); // Restore \\ as single \
145
+ }
146
+
147
+ /**
148
+ * Simple YAML parser for our specific format
149
+ * Handles: comments, key-value, arrays, nested objects with conditions
150
+ */
151
+ function parseSimpleYaml(content) {
152
+ const result = { rules: [], blocked: [], ask: [], paths: {} };
153
+ const lines = content.split('\n');
154
+
155
+ let currentSection = null;
156
+ let currentSubSection = null;
157
+ let currentObject = null;
158
+ let currentConditions = null;
159
+ let currentCondition = null;
160
+
161
+ for (let i = 0; i < lines.length; i++) {
162
+ const line = lines[i];
163
+ const trimmed = line.trim();
164
+
165
+ // Skip empty lines and comments
166
+ if (!trimmed || trimmed.startsWith('#')) {
167
+ continue;
168
+ }
169
+
170
+ // Check indentation level
171
+ const indent = line.search(/\S/);
172
+
173
+ // Top-level key (no indent): rules:, blocked:, ask:, paths:
174
+ if (indent === 0 && trimmed.endsWith(':')) {
175
+ // Save any pending object
176
+ if (currentObject && currentSection) {
177
+ if (currentConditions) {
178
+ currentObject.conditions = currentConditions;
179
+ currentConditions = null;
180
+ }
181
+ result[currentSection].push(currentObject);
182
+ currentObject = null;
183
+ }
184
+
185
+ currentSection = trimmed.slice(0, -1);
186
+ currentSubSection = null;
187
+
188
+ if (currentSection === 'paths' && !result.paths) {
189
+ result.paths = {};
190
+ } else if (!result[currentSection]) {
191
+ result[currentSection] = [];
192
+ }
193
+ continue;
194
+ }
195
+
196
+ // Sub-section under paths (indent 2): zeroAccess:, readOnly:, noDelete:
197
+ if (currentSection === 'paths' && indent === 2 && trimmed.endsWith(':')) {
198
+ currentSubSection = trimmed.slice(0, -1);
199
+ result.paths[currentSubSection] = result.paths[currentSubSection] || [];
200
+ continue;
201
+ }
202
+
203
+ // Handle rules section specially (supports nested conditions array)
204
+ if (currentSection === 'rules') {
205
+ // New rule object (- name: xxx)
206
+ if (indent === 2 && trimmed.startsWith('-')) {
207
+ // Save previous rule
208
+ if (currentObject) {
209
+ if (currentConditions) {
210
+ currentObject.conditions = currentConditions;
211
+ currentConditions = null;
212
+ }
213
+ result.rules.push(currentObject);
214
+ }
215
+
216
+ const value = trimmed.slice(1).trim();
217
+ if (value.includes(':')) {
218
+ const colonIndex = value.indexOf(':');
219
+ const key = value.slice(0, colonIndex).trim();
220
+ const val = value.slice(colonIndex + 1).trim().replace(/^["']|["']$/g, '');
221
+ currentObject = { [key]: val };
222
+ }
223
+ continue;
224
+ }
225
+
226
+ // Rule property (indent 4): event:, action:, message:
227
+ if (currentObject && indent === 4 && trimmed.includes(':') && !trimmed.startsWith('-')) {
228
+ const colonIndex = trimmed.indexOf(':');
229
+ const key = trimmed.slice(0, colonIndex).trim();
230
+ const rawVal = trimmed.slice(colonIndex + 1).trim();
231
+
232
+ if (key === 'conditions' && rawVal === '') {
233
+ // Start conditions array
234
+ currentConditions = [];
235
+ continue;
236
+ }
237
+
238
+ const val = rawVal.replace(/^["']|["']$/g, '');
239
+ currentObject[key] = rawVal.startsWith('"') ? processYamlEscapes(val) : val;
240
+ continue;
241
+ }
242
+
243
+ // Condition array item (indent 6): - field: xxx
244
+ if (currentConditions !== null && indent === 6 && trimmed.startsWith('-')) {
245
+ // Save previous condition
246
+ if (currentCondition) {
247
+ currentConditions.push(currentCondition);
248
+ }
249
+
250
+ const value = trimmed.slice(1).trim();
251
+ if (value.includes(':')) {
252
+ const colonIndex = value.indexOf(':');
253
+ const key = value.slice(0, colonIndex).trim();
254
+ const val = value.slice(colonIndex + 1).trim().replace(/^["']|["']$/g, '');
255
+ currentCondition = { [key]: val };
256
+ }
257
+ continue;
258
+ }
259
+
260
+ // Condition property (indent 8): pattern: xxx
261
+ if (currentCondition && indent === 8 && trimmed.includes(':')) {
262
+ const colonIndex = trimmed.indexOf(':');
263
+ const key = trimmed.slice(0, colonIndex).trim();
264
+ const rawVal = trimmed.slice(colonIndex + 1).trim();
265
+ const val = rawVal.replace(/^["']|["']$/g, '');
266
+ currentCondition[key] = rawVal.startsWith('"') ? processYamlEscapes(val) : val;
267
+
268
+ // Check if next line is still part of condition
269
+ const nextLine = lines[i + 1];
270
+ const nextIndent = nextLine ? nextLine.search(/\S/) : 0;
271
+ if (!nextLine || nextIndent < 8 || (nextLine.trim().startsWith('-') && nextIndent <= 6)) {
272
+ currentConditions.push(currentCondition);
273
+ currentCondition = null;
274
+ }
275
+ continue;
276
+ }
277
+
278
+ continue;
279
+ }
280
+
281
+ // Array item (starts with -)
282
+ if (trimmed.startsWith('-')) {
283
+ const value = trimmed.slice(1).trim();
284
+
285
+ // Check if this is a key-value pair in the array item: - pattern: "..."
286
+ if (value.includes(':') && !value.startsWith('"') && !value.startsWith("'")) {
287
+ // Start of an object in array - split only on first colon to preserve colons in values
288
+ const colonIndex = value.indexOf(':');
289
+ const key = value.slice(0, colonIndex).trim();
290
+ const val = value.slice(colonIndex + 1).trim();
291
+ const rawVal = val.replace(/^["']|["']$/g, '');
292
+ currentObject = { [key]: val.startsWith('"') ? processYamlEscapes(rawVal) : rawVal };
293
+ } else {
294
+ // Simple string value
295
+ const rawValue = value.replace(/^["']|["']$/g, '');
296
+ const cleanValue = value.startsWith('"') ? processYamlEscapes(rawValue) : rawValue;
297
+
298
+ if (currentSection === 'paths' && currentSubSection) {
299
+ result.paths[currentSubSection].push(cleanValue);
300
+ } else if (currentSection && currentObject === null) {
301
+ result[currentSection].push(cleanValue);
302
+ }
303
+ currentObject = null;
304
+ }
305
+ continue;
306
+ }
307
+
308
+ // Continuation of object in array (indent 4+): reason: "..."
309
+ if (currentObject && trimmed.includes(':')) {
310
+ const colonIndex = trimmed.indexOf(':');
311
+ const key = trimmed.slice(0, colonIndex).trim();
312
+ const rawVal = trimmed.slice(colonIndex + 1).trim();
313
+ const strippedVal = rawVal.replace(/^["']|["']$/g, '');
314
+ currentObject[key] = rawVal.startsWith('"') ? processYamlEscapes(strippedVal) : strippedVal;
315
+
316
+ // Check if next line continues this object
317
+ const nextLine = lines[i + 1];
318
+ const nextIndent = nextLine ? nextLine.search(/\S/) : 0;
319
+ const nextTrimmed = nextLine ? nextLine.trim() : '';
320
+
321
+ // If next line is a new array item or less indented, push current object
322
+ if (!nextLine || nextIndent <= 2 || nextTrimmed.startsWith('-')) {
323
+ if (currentSection && currentSection !== 'rules') {
324
+ result[currentSection].push(currentObject);
325
+ }
326
+ currentObject = null;
327
+ }
328
+ }
329
+ }
330
+
331
+ // Don't forget the last object
332
+ if (currentObject && currentSection === 'rules') {
333
+ if (currentConditions) {
334
+ currentObject.conditions = currentConditions;
335
+ }
336
+ result.rules.push(currentObject);
337
+ } else if (currentObject && currentSection && currentSection !== 'rules') {
338
+ result[currentSection].push(currentObject);
339
+ }
340
+
341
+ return result;
342
+ }
343
+
344
+ /**
345
+ * Load damage control patterns from YAML file
346
+ * Supports both new event-based format and legacy format
347
+ */
348
+ function loadPatterns() {
349
+ const config = getConfig();
350
+ const dcConfig = config.damageControl || {};
351
+ const patternsPath = dcConfig.patternsFile
352
+ ? path.join(PROJECT_ROOT, dcConfig.patternsFile)
353
+ : PATTERNS_FILE;
354
+
355
+ if (!fs.existsSync(patternsPath)) {
356
+ return {
357
+ rules: [],
358
+ blocked: [],
359
+ ask: [],
360
+ paths: { zeroAccess: [], readOnly: [], noDelete: [] }
361
+ };
362
+ }
363
+
364
+ try {
365
+ const content = fs.readFileSync(patternsPath, 'utf-8');
366
+ const parsed = parseSimpleYaml(content);
367
+ // Ensure rules array exists
368
+ parsed.rules = parsed.rules || [];
369
+ return parsed;
370
+ } catch (err) {
371
+ console.error(console.error('Error loading damage-control.yaml:', err.message));
372
+ return {
373
+ rules: [],
374
+ blocked: [],
375
+ ask: [],
376
+ paths: { zeroAccess: [], readOnly: [], noDelete: [] }
377
+ };
378
+ }
379
+ }
380
+
381
+ // ============================================================
382
+ // Event-Based Rule Checking
383
+ // ============================================================
384
+
385
+ /**
386
+ * Check if an event matches a rule (AND logic for conditions)
387
+ *
388
+ * @param {object} rule - Rule definition with event, action, conditions
389
+ * @param {string} eventType - Event type (bash/file/stop/prompt)
390
+ * @param {object} context - Event context
391
+ * @returns {string|null} - Action to take or null if no match
392
+ */
393
+ function checkEventRule(rule, eventType, context) {
394
+ // Check event type match
395
+ if (rule.event !== 'all' && rule.event !== eventType) {
396
+ return null;
397
+ }
398
+
399
+ // If no conditions, rule matches all events of this type
400
+ if (!rule.conditions || rule.conditions.length === 0) {
401
+ return rule.action;
402
+ }
403
+
404
+ // Check all conditions (AND logic)
405
+ for (const condition of rule.conditions) {
406
+ const value = context[condition.field];
407
+
408
+ if (value === undefined) {
409
+ return null; // Field doesn't exist
410
+ }
411
+
412
+ const regex = safeRegExp(condition.pattern, 'i');
413
+ if (!regex) {
414
+ return null; // Invalid or unsafe regex, skip this condition
415
+ }
416
+ if (!safeRegexTest(regex, value)) {
417
+ return null; // Condition not met
418
+ }
419
+ }
420
+
421
+ // All conditions matched
422
+ return rule.action;
423
+ }
424
+
425
+ /**
426
+ * Main event check function - checks event against all rules
427
+ *
428
+ * @param {string} eventType - Event type (bash/file/stop/prompt)
429
+ * @param {object} context - Event context
430
+ * @returns {object} - { allowed: boolean, action: string, message: string }
431
+ */
432
+ function checkEvent(eventType, context = {}) {
433
+ const config = getConfig();
434
+ const dcConfig = config.damageControl || {};
435
+
436
+ // Check if damage control is enabled
437
+ if (!dcConfig.enabled) {
438
+ return { allowed: true, action: 'allow', message: 'Damage control disabled' };
439
+ }
440
+
441
+ // Check if this event type is enabled
442
+ const events = dcConfig.events || { bash: true };
443
+ if (events[eventType] === false) {
444
+ return { allowed: true, action: 'allow', message: `Event type '${eventType}' disabled` };
445
+ }
446
+
447
+ const patterns = loadPatterns();
448
+
449
+ // Check event-based rules first (new format)
450
+ for (const rule of patterns.rules || []) {
451
+ const action = checkEventRule(rule, eventType, context);
452
+ if (action) {
453
+ const result = {
454
+ allowed: action === 'allow',
455
+ action,
456
+ message: rule.message || `Rule '${rule.name}' matched`,
457
+ rule: rule.name,
458
+ };
459
+
460
+ // Log if configured
461
+ if (dcConfig.logging) {
462
+ logDamageControl(eventType, context, result);
463
+ }
464
+
465
+ if (action === 'block') {
466
+ return result;
467
+ }
468
+ if (action === 'ask') {
469
+ return { ...result, requiresConfirmation: true };
470
+ }
471
+ if (action === 'warn') {
472
+ console.log(colors.yellow + `Warning: ${result.message}` + colors.reset);
473
+ return { allowed: true, action: 'warn', message: result.message };
474
+ }
475
+ }
476
+ }
477
+
478
+ // Fall back to legacy patterns for bash events
479
+ if (eventType === 'bash') {
480
+ const cmd = context.command || '';
481
+ const legacyResult = checkCommand(cmd);
482
+ if (legacyResult.action !== 'allow') {
483
+ return {
484
+ allowed: legacyResult.action === 'allow',
485
+ ...legacyResult,
486
+ requiresConfirmation: legacyResult.action === 'ask'
487
+ };
488
+ }
489
+ }
490
+
491
+ // Fall back to legacy path patterns for file events
492
+ if (eventType === 'file') {
493
+ const filePath = context.file_path || context.filePath || '';
494
+ const operation = context.operation || 'edit';
495
+ const pathResult = checkPath(filePath, operation);
496
+ if (!pathResult.allowed) {
497
+ return {
498
+ allowed: false,
499
+ action: 'block',
500
+ message: pathResult.reason,
501
+ level: pathResult.level
502
+ };
503
+ }
504
+ }
505
+
506
+ return { allowed: true, action: 'allow', message: 'No rules matched' };
507
+ }
508
+
509
+ /**
510
+ * Log damage control actions
511
+ */
512
+ function logDamageControl(eventType, context, result) {
513
+ const config = getConfig();
514
+ const dcConfig = config.damageControl || {};
515
+
516
+ if (!dcConfig.logging) return;
517
+
518
+ const logDir = path.join(PROJECT_ROOT, '.workflow', 'logs');
519
+ const logPath = path.join(logDir, 'damage-control.log');
520
+
521
+ // Ensure log directory exists
522
+ if (!fs.existsSync(logDir)) {
523
+ fs.mkdirSync(logDir, { recursive: true });
524
+ }
525
+
526
+ const timestamp = new Date().toISOString();
527
+ const action = result.action.toUpperCase().padEnd(7);
528
+ const contextStr = JSON.stringify(context).substring(0, 100);
529
+
530
+ const entry = `${timestamp} | ${action} | ${eventType} | ${contextStr} | ${result.message}\n`;
531
+
532
+ fs.appendFileSync(logPath, entry);
533
+ }
534
+
535
+ // ============================================================
536
+ // Convenience Functions for Event Checking
537
+ // ============================================================
538
+
539
+ /**
540
+ * Check a bash command (convenience wrapper)
541
+ */
542
+ function checkBashEvent(command) {
543
+ return checkEvent('bash', { command });
544
+ }
545
+
546
+ /**
547
+ * Check a file operation (convenience wrapper)
548
+ */
549
+ function checkFileEvent(filePath, operation = 'edit', content = '') {
550
+ return checkEvent('file', { file_path: filePath, filePath, operation, content });
551
+ }
552
+
553
+ /**
554
+ * Check session stop (convenience wrapper)
555
+ */
556
+ function checkStopEvent() {
557
+ return checkEvent('stop', {});
558
+ }
559
+
560
+ /**
561
+ * Check user prompt (convenience wrapper)
562
+ */
563
+ function checkPromptEvent(prompt) {
564
+ return checkEvent('prompt', { user_prompt: prompt });
565
+ }
566
+
567
+ /**
568
+ * Check if command is safe (read-only)
569
+ */
570
+ function isSafeCommand(cmd) {
571
+ const normalizedCmd = cmd.trim();
572
+ return SAFE_COMMANDS.some(pattern => pattern.test(normalizedCmd));
573
+ }
574
+
575
+ /**
576
+ * Check command against patterns
577
+ * Returns: { action: 'allow' | 'block' | 'ask', reason?: string }
578
+ */
579
+ function checkCommand(cmd) {
580
+ const config = getConfig();
581
+ const dcConfig = config.damageControl || {};
582
+
583
+ if (!dcConfig.enabled) {
584
+ return { action: 'allow' };
585
+ }
586
+
587
+ // Skip safe commands
588
+ if (isSafeCommand(cmd)) {
589
+ return { action: 'allow', reason: 'Safe command' };
590
+ }
591
+
592
+ const patterns = loadPatterns();
593
+
594
+ // Check blocked patterns
595
+ for (const pattern of patterns.blocked || []) {
596
+ const regex = safeRegExp(pattern, 'i');
597
+ if (!regex) continue; // Skip invalid/unsafe patterns
598
+ if (regex.test(cmd)) {
599
+ return {
600
+ action: 'block',
601
+ reason: `Matches blocked pattern: ${pattern}`,
602
+ pattern
603
+ };
604
+ }
605
+ }
606
+
607
+ // Check ask patterns
608
+ for (const item of patterns.ask || []) {
609
+ const pattern = typeof item === 'string' ? item : item.pattern;
610
+ const reason = typeof item === 'object' ? item.reason : 'Matches sensitive pattern';
611
+
612
+ const regex = safeRegExp(pattern, 'i');
613
+ if (!regex) continue; // Skip invalid/unsafe patterns
614
+ if (regex.test(cmd)) {
615
+ return {
616
+ action: 'ask',
617
+ reason,
618
+ pattern
619
+ };
620
+ }
621
+ }
622
+
623
+ return { action: 'allow' };
624
+ }
625
+
626
+ /**
627
+ * Check if a path matches a pattern using proper path segment matching
628
+ * Prevents false positives like "node_modules" matching "node_modules_backup"
629
+ */
630
+ function pathMatchesPattern(normalizedPath, pattern) {
631
+ // Normalize pattern too
632
+ const normalizedPattern = pattern.replace(/\\/g, '/');
633
+
634
+ // If pattern is an absolute path or contains path separators, do exact segment matching
635
+ if (normalizedPattern.includes('/')) {
636
+ // Check if the path contains the pattern as a segment sequence
637
+ return normalizedPath.includes(normalizedPattern) &&
638
+ (normalizedPath === normalizedPattern ||
639
+ normalizedPath.startsWith(normalizedPattern + '/') ||
640
+ normalizedPath.endsWith('/' + normalizedPattern) ||
641
+ normalizedPath.includes('/' + normalizedPattern + '/'));
642
+ }
643
+
644
+ // For simple names (no path separator), match as directory/file name segment
645
+ const segments = normalizedPath.split('/');
646
+ return segments.some(segment => segment === normalizedPattern);
647
+ }
648
+
649
+ /**
650
+ * Check if path operation is allowed
651
+ * Returns: { allowed: boolean, reason?: string, level?: string }
652
+ */
653
+ function checkPath(filePath, operation) {
654
+ const config = getConfig();
655
+ const dcConfig = config.damageControl || {};
656
+
657
+ if (!dcConfig.enabled) {
658
+ return { allowed: true };
659
+ }
660
+
661
+ const patterns = loadPatterns();
662
+ const paths = patterns.paths || {};
663
+
664
+ // Normalize path (handle both forward and backslashes)
665
+ const normalizedPath = filePath.replace(/\\/g, '/');
666
+
667
+ // Zero access - block all operations (read, write, delete)
668
+ for (const p of paths.zeroAccess || []) {
669
+ if (pathMatchesPattern(normalizedPath, p)) {
670
+ return {
671
+ allowed: false,
672
+ reason: `Zero access path: ${p}`,
673
+ level: 'zeroAccess'
674
+ };
675
+ }
676
+ }
677
+
678
+ // Read-only - block write/delete
679
+ if (operation === 'write' || operation === 'delete') {
680
+ for (const p of paths.readOnly || []) {
681
+ if (pathMatchesPattern(normalizedPath, p)) {
682
+ return {
683
+ allowed: false,
684
+ reason: `Read-only path: ${p}`,
685
+ level: 'readOnly'
686
+ };
687
+ }
688
+ }
689
+ }
690
+
691
+ // No-delete - block delete only
692
+ if (operation === 'delete') {
693
+ for (const p of paths.noDelete || []) {
694
+ if (pathMatchesPattern(normalizedPath, p)) {
695
+ return {
696
+ allowed: false,
697
+ reason: `No-delete path: ${p}`,
698
+ level: 'noDelete'
699
+ };
700
+ }
701
+ }
702
+ }
703
+
704
+ return { allowed: true };
705
+ }
706
+
707
+ /**
708
+ * AI prompt hook for unknown dangerous commands
709
+ * Returns: { action: 'allow' | 'block' | 'ask', reason?: string }
710
+ *
711
+ * Note: Full implementation requires API integration.
712
+ * This is a stub that can be enhanced with actual AI call.
713
+ */
714
+ async function promptHookCheck(cmd) {
715
+ const config = getConfig();
716
+ const dcConfig = config.damageControl || {};
717
+ const promptConfig = dcConfig.promptHook || {};
718
+
719
+ if (!dcConfig.enabled || !promptConfig.enabled) {
720
+ return { action: 'allow' };
721
+ }
722
+
723
+ // Skip if already caught by patterns
724
+ const patternResult = checkCommand(cmd);
725
+ if (patternResult.action !== 'allow') {
726
+ return patternResult;
727
+ }
728
+
729
+ // Skip safe commands
730
+ if (isSafeCommand(cmd)) {
731
+ return { action: 'allow', reason: 'Safe command' };
732
+ }
733
+
734
+ // TODO: Implement actual AI API call
735
+ // For now, return allow with a note
736
+ return {
737
+ action: 'allow',
738
+ reason: 'Prompt hook enabled but API not yet integrated'
739
+ };
740
+ }
741
+
742
+ /**
743
+ * Get status of damage control system
744
+ */
745
+ function getStatus() {
746
+ const config = getConfig();
747
+ const dcConfig = config.damageControl || {};
748
+ const patterns = loadPatterns();
749
+
750
+ return {
751
+ enabled: dcConfig.enabled || false,
752
+ promptHook: {
753
+ enabled: dcConfig.promptHook?.enabled || false,
754
+ model: dcConfig.promptHook?.model || 'haiku'
755
+ },
756
+ patternsFile: dcConfig.patternsFile || '.workflow/damage-control.yaml',
757
+ events: dcConfig.events || { bash: true, file: false, stop: false, prompt: false },
758
+ patternsLoaded: {
759
+ rules: (patterns.rules || []).length,
760
+ blocked: (patterns.blocked || []).length,
761
+ ask: (patterns.ask || []).length,
762
+ paths: {
763
+ zeroAccess: (patterns.paths?.zeroAccess || []).length,
764
+ readOnly: (patterns.paths?.readOnly || []).length,
765
+ noDelete: (patterns.paths?.noDelete || []).length
766
+ }
767
+ },
768
+ safeCommandPatterns: SAFE_COMMANDS.length
769
+ };
770
+ }
771
+
772
+ // CLI handling
773
+ if (require.main === module) {
774
+ const args = process.argv.slice(2);
775
+ const command = args[0];
776
+
777
+ if (args.includes('--help') || args.includes('-h') || !command) {
778
+ console.log(`
779
+ Wogi-Flow Damage Control
780
+
781
+ Event-based pattern matching for destructive command protection.
782
+ Supports multiple event types: bash, file, stop, prompt.
783
+
784
+ Usage:
785
+ flow damage-control check "<command>" Check if bash command is allowed
786
+ flow damage-control event <type> <ctx> Check event against all rules
787
+ flow damage-control path "<path>" <op> Check if path operation is allowed
788
+ flow damage-control status Show damage control status
789
+ flow damage-control rules Show all rules (event-based + legacy)
790
+
791
+ Event Types: bash, file, stop, prompt
792
+ Operations for path check: read, write, delete
793
+
794
+ Examples:
795
+ flow dc check "rm -rf node_modules"
796
+ flow dc check "git reset --hard"
797
+ flow dc event bash '{"command": "rm -rf /"}'
798
+ flow dc event file '{"file_path": ".env", "operation": "edit"}'
799
+ flow dc path "/home/user/.ssh/id_rsa" read
800
+ flow dc status
801
+ flow dc rules
802
+
803
+ Configuration (config.json):
804
+ "damageControl": {
805
+ "enabled": false,
806
+ "patternsFile": ".workflow/damage-control.yaml",
807
+ "events": {
808
+ "bash": true,
809
+ "file": true,
810
+ "stop": true,
811
+ "prompt": false
812
+ },
813
+ "promptHook": {
814
+ "enabled": false,
815
+ "model": "haiku"
816
+ },
817
+ "logging": true
818
+ }
819
+ `);
820
+ process.exit(0);
821
+ }
822
+
823
+ switch (command) {
824
+ case 'check': {
825
+ const cmd = args.slice(1).join(' ');
826
+ if (!cmd) {
827
+ log('red', 'Error: Command to check is required');
828
+ log('dim', 'Usage: flow dc check "<command>"');
829
+ process.exit(1);
830
+ }
831
+ // Use event-based check (covers both new rules and legacy)
832
+ const result = checkEvent('bash', { command: cmd });
833
+ console.log(JSON.stringify(result, null, 2));
834
+ break;
835
+ }
836
+
837
+ case 'event': {
838
+ const eventType = args[1];
839
+ const contextStr = args[2];
840
+ if (!eventType || !EVENT_TYPES.includes(eventType)) {
841
+ log('red', `Error: Event type must be one of: ${EVENT_TYPES.join(', ')}`);
842
+ process.exit(1);
843
+ }
844
+ let context = {};
845
+ if (contextStr) {
846
+ try {
847
+ context = JSON.parse(contextStr);
848
+ } catch (err) {
849
+ log('red', 'Error: Context must be valid JSON');
850
+ process.exit(1);
851
+ }
852
+ }
853
+ const result = checkEvent(eventType, context);
854
+ console.log(JSON.stringify(result, null, 2));
855
+ process.exit(result.allowed ? 0 : 1);
856
+ break;
857
+ }
858
+
859
+ case 'path': {
860
+ const filePath = args[1];
861
+ const operation = args[2] || 'read';
862
+ if (!filePath) {
863
+ log('red', 'Error: Path is required');
864
+ log('dim', 'Usage: flow dc path "<path>" <operation>');
865
+ process.exit(1);
866
+ }
867
+ const result = checkPath(filePath, operation);
868
+ console.log(JSON.stringify(result, null, 2));
869
+ break;
870
+ }
871
+
872
+ case 'status': {
873
+ const status = getStatus();
874
+ console.log('');
875
+ log('cyan', 'Damage Control Status');
876
+ console.log('');
877
+ log('white', ` Enabled: ${status.enabled ? colors.green + 'Yes' : colors.yellow + 'No'}${colors.reset}`);
878
+ log('white', ` Prompt Hook: ${status.promptHook.enabled ? colors.green + 'Yes' : colors.dim + 'No'}${colors.reset}`);
879
+ log('white', ` Patterns File: ${status.patternsFile}`);
880
+ console.log('');
881
+ log('cyan', 'Event Types:');
882
+ log('white', ` bash: ${status.events.bash ? colors.green + 'ON' : colors.dim + 'OFF'}${colors.reset}`);
883
+ log('white', ` file: ${status.events.file ? colors.green + 'ON' : colors.dim + 'OFF'}${colors.reset}`);
884
+ log('white', ` stop: ${status.events.stop ? colors.green + 'ON' : colors.dim + 'OFF'}${colors.reset}`);
885
+ log('white', ` prompt: ${status.events.prompt ? colors.green + 'ON' : colors.dim + 'OFF'}${colors.reset}`);
886
+ console.log('');
887
+ log('cyan', 'Rules Loaded:');
888
+ log('white', ` Event Rules: ${status.patternsLoaded.rules}`);
889
+ log('white', ` Legacy Blocked: ${status.patternsLoaded.blocked}`);
890
+ log('white', ` Legacy Ask: ${status.patternsLoaded.ask}`);
891
+ log('white', ` Zero Access Paths: ${status.patternsLoaded.paths.zeroAccess}`);
892
+ log('white', ` Read-Only Paths: ${status.patternsLoaded.paths.readOnly}`);
893
+ log('white', ` No-Delete Paths: ${status.patternsLoaded.paths.noDelete}`);
894
+ log('white', ` Safe Command Patterns: ${status.safeCommandPatterns}`);
895
+ console.log('');
896
+
897
+ if (!status.enabled) {
898
+ log('dim', 'To enable: Set damageControl.enabled to true in config.json');
899
+ }
900
+ break;
901
+ }
902
+
903
+ case 'patterns':
904
+ case 'rules': {
905
+ const patterns = loadPatterns();
906
+ console.log('');
907
+
908
+ // Show event-based rules first (new format)
909
+ if (patterns.rules && patterns.rules.length > 0) {
910
+ log('cyan', 'Event-Based Rules:');
911
+ for (const rule of patterns.rules) {
912
+ const eventColor = rule.event === 'all' ? 'cyan' : 'yellow';
913
+ const actionColor = rule.action === 'block' ? 'red' : rule.action === 'warn' ? 'yellow' : 'green';
914
+ log(eventColor, ` [${rule.event}] ${rule.name}`);
915
+ log(actionColor, ` Action: ${rule.action}`);
916
+ if (rule.conditions && rule.conditions.length > 0) {
917
+ log('dim', ' Conditions:');
918
+ for (const c of rule.conditions) {
919
+ log('dim', ` ${c.field}: /${c.pattern}/`);
920
+ }
921
+ }
922
+ if (rule.message) {
923
+ log('dim', ` Message: ${rule.message}`);
924
+ }
925
+ }
926
+ console.log('');
927
+ }
928
+
929
+ // Show legacy patterns
930
+ log('cyan', 'Legacy Blocked Patterns:');
931
+ (patterns.blocked || []).forEach(p => log('red', ` - ${p}`));
932
+ console.log('');
933
+ log('cyan', 'Legacy Ask Patterns:');
934
+ (patterns.ask || []).forEach(p => {
935
+ if (typeof p === 'object') {
936
+ log('yellow', ` - ${p.pattern}`);
937
+ log('dim', ` Reason: ${p.reason}`);
938
+ } else {
939
+ log('yellow', ` - ${p}`);
940
+ }
941
+ });
942
+ console.log('');
943
+ log('cyan', 'Protected Paths:');
944
+ log('white', ' Zero Access:');
945
+ (patterns.paths?.zeroAccess || []).forEach(p => log('red', ` - ${p}`));
946
+ log('white', ' Read-Only:');
947
+ (patterns.paths?.readOnly || []).forEach(p => log('yellow', ` - ${p}`));
948
+ log('white', ' No-Delete:');
949
+ (patterns.paths?.noDelete || []).forEach(p => log('yellow', ` - ${p}`));
950
+ break;
951
+ }
952
+
953
+ default:
954
+ log('red', `Unknown command: ${command}`);
955
+ log('dim', 'Run: flow dc --help for usage');
956
+ process.exit(1);
957
+ }
958
+ }
959
+
960
+ // Export for use by other modules
961
+ module.exports = {
962
+ // Event-based checking (new)
963
+ checkEvent,
964
+ checkEventRule,
965
+ checkBashEvent,
966
+ checkFileEvent,
967
+ checkStopEvent,
968
+ checkPromptEvent,
969
+ EVENT_TYPES,
970
+ ACTIONS,
971
+ // Legacy functions (still supported)
972
+ loadPatterns,
973
+ parseSimpleYaml,
974
+ isSafeCommand,
975
+ checkCommand,
976
+ checkPath,
977
+ promptHookCheck,
978
+ getStatus,
979
+ SAFE_COMMANDS,
980
+ // Regex safety utilities (for use by other modules)
981
+ safeRegExp,
982
+ safeRegexTest,
983
+ MAX_REGEX_LENGTH,
984
+ MAX_INPUT_LENGTH
985
+ };