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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- 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
|