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,587 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Safety Guardrails
5
+ *
6
+ * Provides permission models and hard limits to prevent unintended operations.
7
+ * Includes file/command allow/deny lists and bounded execution limits.
8
+ *
9
+ * Usage as module:
10
+ * const { SafetyGuard, SafetyError } = require('./flow-safety');
11
+ * const guard = new SafetyGuard(config);
12
+ * guard.checkFilePermission('/path/to/file');
13
+ *
14
+ * Usage as CLI:
15
+ * flow safety check-file <path> # Check if file access is allowed
16
+ * flow safety check-command <cmd> # Check if command is allowed
17
+ * flow safety status # Show current limits and permissions
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const { getProjectRoot, colors: c } = require('./flow-utils');
23
+
24
+ const PROJECT_ROOT = getProjectRoot();
25
+ const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
26
+ const CONFIG_PATH = path.join(WORKFLOW_DIR, 'config.json');
27
+
28
+ /**
29
+ * Custom error for safety violations
30
+ */
31
+ class SafetyError extends Error {
32
+ constructor(message, type = 'general') {
33
+ super(message);
34
+ this.name = 'SafetyError';
35
+ this.type = type;
36
+ this.isSafetyViolation = true;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Default safety configuration
42
+ */
43
+ const DEFAULT_SAFETY_CONFIG = {
44
+ enabled: true,
45
+ limits: {
46
+ maxSteps: 50,
47
+ maxFilesModified: 20,
48
+ maxFilesCreated: 10,
49
+ maxFilesDeleted: 5,
50
+ maxTokens: null,
51
+ checkpointInterval: 5,
52
+ maxCommandsRun: 30
53
+ },
54
+ permissions: {
55
+ files: {
56
+ allow: [
57
+ 'src/**',
58
+ 'lib/**',
59
+ 'tests/**',
60
+ 'test/**',
61
+ '__tests__/**',
62
+ 'scripts/**',
63
+ '.workflow/**',
64
+ 'templates/**',
65
+ 'config/**',
66
+ '*.json',
67
+ '*.md',
68
+ '*.ts',
69
+ '*.tsx',
70
+ '*.js',
71
+ '*.jsx',
72
+ '*.css',
73
+ '*.scss',
74
+ '*.html',
75
+ '*.yaml',
76
+ '*.yml'
77
+ ],
78
+ deny: [
79
+ '**/.env',
80
+ '**/.env.*',
81
+ '**/.env.local',
82
+ '**/secrets/**',
83
+ '**/credentials/**',
84
+ '**/*.pem',
85
+ '**/*.key',
86
+ '**/*.crt',
87
+ '**/id_rsa*',
88
+ '**/id_ed25519*',
89
+ '**/.ssh/**',
90
+ '**/.aws/**',
91
+ '**/.gcloud/**',
92
+ '**/package-lock.json',
93
+ '**/yarn.lock',
94
+ '**/pnpm-lock.yaml',
95
+ '**/node_modules/**',
96
+ '**/.git/**',
97
+ '**/dist/**',
98
+ '**/build/**'
99
+ ]
100
+ },
101
+ commands: {
102
+ allow: [
103
+ 'npm',
104
+ 'npx',
105
+ 'yarn',
106
+ 'pnpm',
107
+ 'bun',
108
+ 'node',
109
+ 'tsc',
110
+ 'tsx',
111
+ 'ts-node',
112
+ 'eslint',
113
+ 'prettier',
114
+ 'biome',
115
+ 'jest',
116
+ 'vitest',
117
+ 'mocha',
118
+ 'pytest',
119
+ 'git',
120
+ 'echo',
121
+ 'cat',
122
+ 'ls',
123
+ 'mkdir',
124
+ 'cp',
125
+ 'mv',
126
+ 'touch',
127
+ 'head',
128
+ 'tail',
129
+ 'grep',
130
+ 'find',
131
+ 'wc',
132
+ 'sort',
133
+ 'uniq',
134
+ 'diff'
135
+ ],
136
+ deny: [
137
+ 'rm -rf /',
138
+ 'rm -rf ~',
139
+ 'rm -rf .',
140
+ 'rm -rf ..',
141
+ 'rm -rf *',
142
+ 'sudo',
143
+ 'su',
144
+ 'chmod 777',
145
+ 'curl',
146
+ 'wget',
147
+ 'ssh',
148
+ 'scp',
149
+ 'rsync',
150
+ 'nc',
151
+ 'netcat',
152
+ 'telnet',
153
+ 'ftp',
154
+ 'eval',
155
+ 'exec',
156
+ ':(){:|:&};:'
157
+ ]
158
+ },
159
+ network: false
160
+ },
161
+ onViolation: 'abort'
162
+ };
163
+
164
+ /**
165
+ * Safety guard class for enforcing limits and permissions
166
+ */
167
+ class SafetyGuard {
168
+ constructor(config = {}) {
169
+ const safetyConfig = config.safety || {};
170
+ this.config = this.mergeConfig(DEFAULT_SAFETY_CONFIG, safetyConfig);
171
+ this.enabled = this.config.enabled;
172
+
173
+ // Counters
174
+ this.counters = {
175
+ steps: 0,
176
+ filesModified: 0,
177
+ filesCreated: 0,
178
+ filesDeleted: 0,
179
+ commandsRun: 0,
180
+ tokensUsed: 0
181
+ };
182
+
183
+ // Track modified files to prevent double-counting
184
+ this.modifiedFiles = new Set();
185
+ this.createdFiles = new Set();
186
+ this.deletedFiles = new Set();
187
+ }
188
+
189
+ /**
190
+ * Deep merge configuration with defaults
191
+ */
192
+ mergeConfig(defaults, override) {
193
+ const result = { ...defaults };
194
+ for (const key of Object.keys(override)) {
195
+ if (override[key] && typeof override[key] === 'object' && !Array.isArray(override[key])) {
196
+ result[key] = this.mergeConfig(defaults[key] || {}, override[key]);
197
+ } else if (override[key] !== undefined) {
198
+ result[key] = override[key];
199
+ }
200
+ }
201
+ return result;
202
+ }
203
+
204
+ /**
205
+ * Check if file access is allowed
206
+ */
207
+ checkFilePermission(filePath, operation = 'read') {
208
+ if (!this.enabled) return true;
209
+
210
+ const permissions = this.config.permissions?.files || {};
211
+ const allowPatterns = permissions.allow || ['**/*'];
212
+ const denyPatterns = permissions.deny || [];
213
+
214
+ // Normalize path
215
+ const normalizedPath = filePath.startsWith('/')
216
+ ? path.relative(PROJECT_ROOT, filePath)
217
+ : filePath;
218
+
219
+ // Check deny list first (takes precedence)
220
+ for (const pattern of denyPatterns) {
221
+ if (this.matchPattern(normalizedPath, pattern)) {
222
+ throw new SafetyError(
223
+ `File access denied by safety policy: ${normalizedPath} (matches deny pattern: ${pattern})`,
224
+ 'file_permission'
225
+ );
226
+ }
227
+ }
228
+
229
+ // Check allow list
230
+ let allowed = false;
231
+ for (const pattern of allowPatterns) {
232
+ if (this.matchPattern(normalizedPath, pattern)) {
233
+ allowed = true;
234
+ break;
235
+ }
236
+ }
237
+
238
+ if (!allowed) {
239
+ throw new SafetyError(
240
+ `File access denied: ${normalizedPath} (not in allow list)`,
241
+ 'file_permission'
242
+ );
243
+ }
244
+
245
+ return true;
246
+ }
247
+
248
+ /**
249
+ * Check if command is allowed
250
+ */
251
+ checkCommandPermission(command) {
252
+ if (!this.enabled) return true;
253
+
254
+ const permissions = this.config.permissions?.commands || {};
255
+ const allowList = permissions.allow || ['*'];
256
+ const denyPatterns = permissions.deny || [];
257
+
258
+ // Get base command
259
+ const baseCmd = command.trim().split(/\s+/)[0];
260
+
261
+ // Check deny list first
262
+ for (const pattern of denyPatterns) {
263
+ if (command.includes(pattern) || command.startsWith(pattern)) {
264
+ throw new SafetyError(
265
+ `Command blocked by safety policy: "${command}" (matches deny pattern: "${pattern}")`,
266
+ 'command_permission'
267
+ );
268
+ }
269
+ }
270
+
271
+ // Check allow list
272
+ const allowed = allowList.includes('*') || allowList.includes(baseCmd);
273
+
274
+ if (!allowed) {
275
+ throw new SafetyError(
276
+ `Command not allowed: "${baseCmd}" (not in allow list)`,
277
+ 'command_permission'
278
+ );
279
+ }
280
+
281
+ return true;
282
+ }
283
+
284
+ /**
285
+ * Check all limits
286
+ */
287
+ checkLimits() {
288
+ if (!this.enabled) return true;
289
+
290
+ const limits = this.config.limits || {};
291
+
292
+ if (limits.maxSteps && this.counters.steps >= limits.maxSteps) {
293
+ throw new SafetyError(
294
+ `Step limit reached (${limits.maxSteps})`,
295
+ 'limit_exceeded'
296
+ );
297
+ }
298
+
299
+ if (limits.maxFilesModified && this.counters.filesModified >= limits.maxFilesModified) {
300
+ throw new SafetyError(
301
+ `File modification limit reached (${limits.maxFilesModified})`,
302
+ 'limit_exceeded'
303
+ );
304
+ }
305
+
306
+ if (limits.maxFilesCreated && this.counters.filesCreated >= limits.maxFilesCreated) {
307
+ throw new SafetyError(
308
+ `File creation limit reached (${limits.maxFilesCreated})`,
309
+ 'limit_exceeded'
310
+ );
311
+ }
312
+
313
+ if (limits.maxFilesDeleted && this.counters.filesDeleted >= limits.maxFilesDeleted) {
314
+ throw new SafetyError(
315
+ `File deletion limit reached (${limits.maxFilesDeleted})`,
316
+ 'limit_exceeded'
317
+ );
318
+ }
319
+
320
+ if (limits.maxCommandsRun && this.counters.commandsRun >= limits.maxCommandsRun) {
321
+ throw new SafetyError(
322
+ `Command execution limit reached (${limits.maxCommandsRun})`,
323
+ 'limit_exceeded'
324
+ );
325
+ }
326
+
327
+ if (limits.maxTokens && this.counters.tokensUsed >= limits.maxTokens) {
328
+ throw new SafetyError(
329
+ `Token limit reached (${limits.maxTokens})`,
330
+ 'limit_exceeded'
331
+ );
332
+ }
333
+
334
+ return true;
335
+ }
336
+
337
+ /**
338
+ * Record a step execution
339
+ */
340
+ recordStep() {
341
+ this.counters.steps++;
342
+ this.checkLimits();
343
+ return this.counters.steps;
344
+ }
345
+
346
+ /**
347
+ * Record a file modification
348
+ */
349
+ recordFileModification(filePath, isNew = false) {
350
+ // Check permission first
351
+ this.checkFilePermission(filePath, isNew ? 'create' : 'modify');
352
+
353
+ if (isNew) {
354
+ if (!this.createdFiles.has(filePath)) {
355
+ this.createdFiles.add(filePath);
356
+ this.counters.filesCreated++;
357
+ }
358
+ } else {
359
+ if (!this.modifiedFiles.has(filePath)) {
360
+ this.modifiedFiles.add(filePath);
361
+ this.counters.filesModified++;
362
+ }
363
+ }
364
+
365
+ this.checkLimits();
366
+ }
367
+
368
+ /**
369
+ * Record a file deletion
370
+ */
371
+ recordFileDeletion(filePath) {
372
+ this.checkFilePermission(filePath, 'delete');
373
+
374
+ if (!this.deletedFiles.has(filePath)) {
375
+ this.deletedFiles.add(filePath);
376
+ this.counters.filesDeleted++;
377
+ }
378
+
379
+ this.checkLimits();
380
+ }
381
+
382
+ /**
383
+ * Record a command execution
384
+ */
385
+ recordCommand(command) {
386
+ this.checkCommandPermission(command);
387
+ this.counters.commandsRun++;
388
+ this.checkLimits();
389
+ }
390
+
391
+ /**
392
+ * Record token usage
393
+ */
394
+ recordTokens(count) {
395
+ this.counters.tokensUsed += count;
396
+ this.checkLimits();
397
+ }
398
+
399
+ /**
400
+ * Check if checkpoint is needed
401
+ */
402
+ needsCheckpoint() {
403
+ const interval = this.config.limits?.checkpointInterval || 5;
404
+ return this.counters.steps > 0 && this.counters.steps % interval === 0;
405
+ }
406
+
407
+ /**
408
+ * Get current status
409
+ */
410
+ getStatus() {
411
+ return {
412
+ enabled: this.enabled,
413
+ counters: { ...this.counters },
414
+ limits: this.config.limits,
415
+ filesModified: Array.from(this.modifiedFiles),
416
+ filesCreated: Array.from(this.createdFiles),
417
+ filesDeleted: Array.from(this.deletedFiles)
418
+ };
419
+ }
420
+
421
+ /**
422
+ * Reset counters (for new run)
423
+ */
424
+ reset() {
425
+ this.counters = {
426
+ steps: 0,
427
+ filesModified: 0,
428
+ filesCreated: 0,
429
+ filesDeleted: 0,
430
+ commandsRun: 0,
431
+ tokensUsed: 0
432
+ };
433
+ this.modifiedFiles.clear();
434
+ this.createdFiles.clear();
435
+ this.deletedFiles.clear();
436
+ }
437
+
438
+ /**
439
+ * Match path against glob pattern
440
+ */
441
+ matchPattern(str, pattern) {
442
+ // Handle ** (match anything including /)
443
+ // Handle * (match anything except /)
444
+ // Handle ? (match single character)
445
+ const regexPattern = pattern
446
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars
447
+ .replace(/\*\*/g, '<<<GLOBSTAR>>>') // Temporarily replace **
448
+ .replace(/\*/g, '[^/]*') // * matches anything except /
449
+ .replace(/<<<GLOBSTAR>>>/g, '.*') // ** matches anything
450
+ .replace(/\?/g, '.'); // ? matches single char
451
+
452
+ const regex = new RegExp(`^${regexPattern}$`);
453
+ return regex.test(str);
454
+ }
455
+ }
456
+
457
+ /**
458
+ * Load safety configuration from config.json
459
+ */
460
+ function loadSafetyConfig() {
461
+ if (fs.existsSync(CONFIG_PATH)) {
462
+ try {
463
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
464
+ return config.safety || {};
465
+ } catch {
466
+ return {};
467
+ }
468
+ }
469
+ return {};
470
+ }
471
+
472
+ /**
473
+ * Format status for display
474
+ */
475
+ function formatStatus(status) {
476
+ const limits = status.limits || {};
477
+
478
+ let output = `${c.cyan}${c.bold}Safety Guardrails Status${c.reset}\n`;
479
+ output += `${'─'.repeat(50)}\n\n`;
480
+
481
+ output += `${c.bold}Status:${c.reset} ${status.enabled ? `${c.green}ENABLED${c.reset}` : `${c.yellow}DISABLED${c.reset}`}\n\n`;
482
+
483
+ output += `${c.bold}Current Counters:${c.reset}\n`;
484
+ output += ` Steps: ${status.counters.steps}/${limits.maxSteps || '∞'}\n`;
485
+ output += ` Files Modified: ${status.counters.filesModified}/${limits.maxFilesModified || '∞'}\n`;
486
+ output += ` Files Created: ${status.counters.filesCreated}/${limits.maxFilesCreated || '∞'}\n`;
487
+ output += ` Files Deleted: ${status.counters.filesDeleted}/${limits.maxFilesDeleted || '∞'}\n`;
488
+ output += ` Commands Run: ${status.counters.commandsRun}/${limits.maxCommandsRun || '∞'}\n`;
489
+ output += ` Tokens Used: ${status.counters.tokensUsed}/${limits.maxTokens || '∞'}\n`;
490
+ output += ` Checkpoint Every: ${limits.checkpointInterval || 5} steps\n`;
491
+
492
+ return output;
493
+ }
494
+
495
+ // Module exports
496
+ module.exports = {
497
+ SafetyGuard,
498
+ SafetyError,
499
+ loadSafetyConfig,
500
+ DEFAULT_SAFETY_CONFIG
501
+ };
502
+
503
+ // CLI Handler
504
+ if (require.main === module) {
505
+ const args = process.argv.slice(2);
506
+ const command = args[0];
507
+
508
+ const safetyConfig = loadSafetyConfig();
509
+ const guard = new SafetyGuard({ safety: safetyConfig });
510
+
511
+ try {
512
+ switch (command) {
513
+ case 'check-file': {
514
+ const filePath = args[1];
515
+ if (!filePath) {
516
+ console.error(`${c.red}Error: File path required${c.reset}`);
517
+ process.exit(1);
518
+ }
519
+ guard.checkFilePermission(filePath);
520
+ console.log(`${c.green}✅ File access allowed: ${filePath}${c.reset}`);
521
+ break;
522
+ }
523
+
524
+ case 'check-command': {
525
+ const cmd = args.slice(1).join(' ');
526
+ if (!cmd) {
527
+ console.error(`${c.red}Error: Command required${c.reset}`);
528
+ process.exit(1);
529
+ }
530
+ guard.checkCommandPermission(cmd);
531
+ console.log(`${c.green}✅ Command allowed: ${cmd}${c.reset}`);
532
+ break;
533
+ }
534
+
535
+ case 'status': {
536
+ console.log(formatStatus(guard.getStatus()));
537
+ break;
538
+ }
539
+
540
+ case 'config': {
541
+ console.log(JSON.stringify(guard.config, null, 2));
542
+ break;
543
+ }
544
+
545
+ default: {
546
+ console.log(`
547
+ ${c.cyan}Wogi Flow - Safety Guardrails${c.reset}
548
+
549
+ ${c.bold}Usage:${c.reset}
550
+ flow safety check-file <path> Check if file access is allowed
551
+ flow safety check-command <cmd> Check if command is allowed
552
+ flow safety status Show current limits and permissions
553
+ flow safety config Show safety configuration (JSON)
554
+
555
+ ${c.bold}Configuration:${c.reset}
556
+ Add to .workflow/config.json:
557
+ {
558
+ "safety": {
559
+ "enabled": true,
560
+ "limits": {
561
+ "maxSteps": 50,
562
+ "maxFilesModified": 20,
563
+ "checkpointInterval": 5
564
+ },
565
+ "permissions": {
566
+ "files": {
567
+ "allow": ["src/**", "tests/**"],
568
+ "deny": ["**/.env", "**/secrets/**"]
569
+ },
570
+ "commands": {
571
+ "allow": ["npm", "node", "git"],
572
+ "deny": ["rm -rf"]
573
+ }
574
+ }
575
+ }
576
+ }
577
+ `);
578
+ }
579
+ }
580
+ } catch (err) {
581
+ if (err.isSafetyViolation) {
582
+ console.error(`${c.red}❌ Safety Violation: ${err.message}${c.reset}`);
583
+ process.exit(5); // Safety violation exit code
584
+ }
585
+ throw err;
586
+ }
587
+ }
@@ -0,0 +1,104 @@
1
+ #!/bin/bash
2
+
3
+ # Wogi Flow - Search Request Log
4
+ # Search for entries by tag or keyword
5
+
6
+ set -e
7
+
8
+ WORKFLOW_DIR=".workflow"
9
+ REQUEST_LOG="$WORKFLOW_DIR/state/request-log.md"
10
+
11
+ # Colors
12
+ GREEN='\033[0;32m'
13
+ YELLOW='\033[1;33m'
14
+ CYAN='\033[0;36m'
15
+ RED='\033[0;31m'
16
+ NC='\033[0m'
17
+
18
+ show_help() {
19
+ echo "Search Request Log"
20
+ echo ""
21
+ echo "Usage: flow search <query> [options]"
22
+ echo ""
23
+ echo "Options:"
24
+ echo " -t, --tag Search by tag (e.g., #screen:login)"
25
+ echo " -r, --request Search in request text"
26
+ echo " -f, --file Search in files changed"
27
+ echo " -n, --limit Limit results (default: 10)"
28
+ echo ""
29
+ echo "Examples:"
30
+ echo " flow search \"#screen:login\""
31
+ echo " flow search \"#component:Button\""
32
+ echo " flow search \"password\" --request"
33
+ echo " flow search \"LoginForm\" --file"
34
+ echo " flow search \"#feature:auth\" --limit 20"
35
+ }
36
+
37
+ if [ -z "$1" ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
38
+ show_help
39
+ exit 0
40
+ fi
41
+
42
+ QUERY="$1"
43
+ LIMIT=10
44
+ SEARCH_TYPE="all"
45
+
46
+ # Parse options
47
+ shift
48
+ while [ $# -gt 0 ]; do
49
+ case "$1" in
50
+ -t|--tag)
51
+ SEARCH_TYPE="tag"
52
+ ;;
53
+ -r|--request)
54
+ SEARCH_TYPE="request"
55
+ ;;
56
+ -f|--file)
57
+ SEARCH_TYPE="file"
58
+ ;;
59
+ -n|--limit)
60
+ shift
61
+ LIMIT="$1"
62
+ ;;
63
+ *)
64
+ ;;
65
+ esac
66
+ shift
67
+ done
68
+
69
+ if [ ! -f "$REQUEST_LOG" ]; then
70
+ echo -e "${RED}Error: No request-log found${NC}"
71
+ exit 1
72
+ fi
73
+
74
+ echo -e "${CYAN}Searching for: $QUERY${NC}"
75
+ echo ""
76
+
77
+ # Search and display results
78
+ results=$(grep -B1 -A6 "$QUERY" "$REQUEST_LOG" 2>/dev/null | head -n $((LIMIT * 8)) || true)
79
+
80
+ if [ -n "$results" ]; then
81
+ echo "$results" | while IFS= read -r line; do
82
+ if [[ "$line" == "### R-"* ]]; then
83
+ echo -e "${GREEN}$line${NC}"
84
+ elif [[ "$line" == "**Type"* ]]; then
85
+ echo -e "${YELLOW}$line${NC}"
86
+ elif [[ "$line" == "**Tags"* ]]; then
87
+ echo -e "${CYAN}$line${NC}"
88
+ else
89
+ echo "$line"
90
+ fi
91
+ done
92
+ echo ""
93
+
94
+ count=$(grep -c "$QUERY" "$REQUEST_LOG" 2>/dev/null || echo "0")
95
+ echo -e "Found: ${GREEN}$count${NC} matching entries"
96
+ else
97
+ echo -e "${YELLOW}No results found for: $QUERY${NC}"
98
+ echo ""
99
+ echo "Try searching for:"
100
+ echo " #screen:[name]"
101
+ echo " #component:[name]"
102
+ echo " #feature:[name]"
103
+ echo " #bug:[id]"
104
+ fi